diff --git a/Cargo.toml b/Cargo.toml index c2172f16..1c25c349 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ actix-rt = { version = "2.2.0" } maplit = { version = "^1" } rust_decimal_macros = { version = "^1" } tracing-subscriber = { version = "0.3", features = ["env-filter"] } -sea-orm = { path = ".", features = ["mock", "debug-print", "tests-cfg"] } +sea-orm = { path = ".", features = ["mock", "debug-print", "tests-cfg", "postgres-array"] } pretty_assertions = { version = "^0.7" } time = { version = "^0.3", features = ["macros"] } @@ -76,6 +76,7 @@ with-chrono = ["chrono", "sea-query/with-chrono", "sea-query-binder?/with-chrono with-rust_decimal = ["rust_decimal", "sea-query/with-rust_decimal", "sea-query-binder?/with-rust_decimal", "sqlx?/decimal"] with-uuid = ["uuid", "sea-query/with-uuid", "sea-query-binder?/with-uuid", "sqlx?/uuid"] with-time = ["time", "sea-query/with-time", "sea-query-binder?/with-time", "sqlx?/time"] +postgres-array = ["sea-query/postgres-array", "sea-query-binder?/postgres-array"] sqlx-dep = [] sqlx-all = ["sqlx-mysql", "sqlx-postgres", "sqlx-sqlite"] sqlx-mysql = ["sqlx-dep", "sea-query-binder/sqlx-mysql", "sqlx/mysql"] diff --git a/sea-orm-cli/Cargo.toml b/sea-orm-cli/Cargo.toml index e63949e4..19810f19 100644 --- a/sea-orm-cli/Cargo.toml +++ b/sea-orm-cli/Cargo.toml @@ -35,7 +35,7 @@ clap = { version = "^3.2", features = ["env", "derive"] } dotenvy = { version = "^0.15", optional = true } async-std = { version = "^1.9", features = [ "attributes", "tokio1" ], optional = true } sea-orm-codegen = { version = "^0.10.0", path = "../sea-orm-codegen", optional = true } -sea-schema = { version = "^0.10.0" } +sea-schema = { version = "^0.10.1" } sqlx = { version = "^0.6", default-features = false, features = [ "mysql", "postgres" ], optional = true } tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing = { version = "0.1" } diff --git a/sea-orm-codegen/src/entity/column.rs b/sea-orm-codegen/src/entity/column.rs index 3c4d739c..298176f4 100644 --- a/sea-orm-codegen/src/entity/column.rs +++ b/sea-orm-codegen/src/entity/column.rs @@ -28,53 +28,59 @@ impl Column { } pub fn get_rs_type(&self, date_time_crate: &DateTimeCrate) -> TokenStream { - #[allow(unreachable_patterns)] - let ident: TokenStream = match &self.col_type { - ColumnType::Char(_) - | ColumnType::String(_) - | ColumnType::Text - | ColumnType::Custom(_) => "String".to_owned(), - ColumnType::TinyInteger(_) => "i8".to_owned(), - ColumnType::SmallInteger(_) => "i16".to_owned(), - ColumnType::Integer(_) => "i32".to_owned(), - ColumnType::BigInteger(_) => "i64".to_owned(), - ColumnType::TinyUnsigned(_) => "u8".to_owned(), - ColumnType::SmallUnsigned(_) => "u16".to_owned(), - ColumnType::Unsigned(_) => "u32".to_owned(), - ColumnType::BigUnsigned(_) => "u64".to_owned(), - ColumnType::Float(_) => "f32".to_owned(), - ColumnType::Double(_) => "f64".to_owned(), - ColumnType::Json | ColumnType::JsonBinary => "Json".to_owned(), - ColumnType::Date => match date_time_crate { - DateTimeCrate::Chrono => "Date".to_owned(), - DateTimeCrate::Time => "TimeDate".to_owned(), - }, - ColumnType::Time(_) => match date_time_crate { - DateTimeCrate::Chrono => "Time".to_owned(), - DateTimeCrate::Time => "TimeTime".to_owned(), - }, - ColumnType::DateTime(_) => match date_time_crate { - DateTimeCrate::Chrono => "DateTime".to_owned(), - DateTimeCrate::Time => "TimeDateTime".to_owned(), - }, - ColumnType::Timestamp(_) => match date_time_crate { - DateTimeCrate::Chrono => "DateTimeUtc".to_owned(), - // ColumnType::Timpestamp(_) => time::PrimitiveDateTime: https://docs.rs/sqlx/0.3.5/sqlx/postgres/types/index.html#time - DateTimeCrate::Time => "TimeDateTime".to_owned(), - }, - ColumnType::TimestampWithTimeZone(_) => match date_time_crate { - DateTimeCrate::Chrono => "DateTimeWithTimeZone".to_owned(), - DateTimeCrate::Time => "TimeDateTimeWithTimeZone".to_owned(), - }, - ColumnType::Decimal(_) | ColumnType::Money(_) => "Decimal".to_owned(), - ColumnType::Uuid => "Uuid".to_owned(), - ColumnType::Binary(_) | ColumnType::VarBinary(_) => "Vec".to_owned(), - ColumnType::Boolean => "bool".to_owned(), - ColumnType::Enum { name, .. } => name.to_string().to_camel_case(), - _ => unimplemented!(), + fn write_rs_type(col_type: &ColumnType, date_time_crate: &DateTimeCrate) -> String { + #[allow(unreachable_patterns)] + match col_type { + ColumnType::Char(_) + | ColumnType::String(_) + | ColumnType::Text + | ColumnType::Custom(_) => "String".to_owned(), + ColumnType::TinyInteger(_) => "i8".to_owned(), + ColumnType::SmallInteger(_) => "i16".to_owned(), + ColumnType::Integer(_) => "i32".to_owned(), + ColumnType::BigInteger(_) => "i64".to_owned(), + ColumnType::TinyUnsigned(_) => "u8".to_owned(), + ColumnType::SmallUnsigned(_) => "u16".to_owned(), + ColumnType::Unsigned(_) => "u32".to_owned(), + ColumnType::BigUnsigned(_) => "u64".to_owned(), + ColumnType::Float(_) => "f32".to_owned(), + ColumnType::Double(_) => "f64".to_owned(), + ColumnType::Json | ColumnType::JsonBinary => "Json".to_owned(), + ColumnType::Date => match date_time_crate { + DateTimeCrate::Chrono => "Date".to_owned(), + DateTimeCrate::Time => "TimeDate".to_owned(), + }, + ColumnType::Time(_) => match date_time_crate { + DateTimeCrate::Chrono => "Time".to_owned(), + DateTimeCrate::Time => "TimeTime".to_owned(), + }, + ColumnType::DateTime(_) => match date_time_crate { + DateTimeCrate::Chrono => "DateTime".to_owned(), + DateTimeCrate::Time => "TimeDateTime".to_owned(), + }, + ColumnType::Timestamp(_) => match date_time_crate { + DateTimeCrate::Chrono => "DateTimeUtc".to_owned(), + // ColumnType::Timpestamp(_) => time::PrimitiveDateTime: https://docs.rs/sqlx/0.3.5/sqlx/postgres/types/index.html#time + DateTimeCrate::Time => "TimeDateTime".to_owned(), + }, + ColumnType::TimestampWithTimeZone(_) => match date_time_crate { + DateTimeCrate::Chrono => "DateTimeWithTimeZone".to_owned(), + DateTimeCrate::Time => "TimeDateTimeWithTimeZone".to_owned(), + }, + ColumnType::Decimal(_) | ColumnType::Money(_) => "Decimal".to_owned(), + ColumnType::Uuid => "Uuid".to_owned(), + ColumnType::Binary(_) | ColumnType::VarBinary(_) => "Vec".to_owned(), + ColumnType::Boolean => "bool".to_owned(), + ColumnType::Enum { name, .. } => name.to_string().to_camel_case(), + ColumnType::Array(column_type) => { + format!("Vec<{}>", write_rs_type(column_type, date_time_crate)) + } + _ => unimplemented!(), + } } - .parse() - .unwrap(); + let ident: TokenStream = write_rs_type(&self.col_type, date_time_crate) + .parse() + .unwrap(); match self.not_null { true => quote! { #ident }, false => quote! { Option<#ident> }, @@ -97,62 +103,72 @@ impl Column { } pub fn get_def(&self) -> TokenStream { - let mut col_def = match &self.col_type { - ColumnType::Char(s) => match s { - Some(s) => quote! { ColumnType::Char(Some(#s)).def() }, - None => quote! { ColumnType::Char(None).def() }, - }, - ColumnType::String(s) => match s { - Some(s) => quote! { ColumnType::String(Some(#s)).def() }, - None => quote! { ColumnType::String(None).def() }, - }, - ColumnType::Text => quote! { ColumnType::Text.def() }, - ColumnType::TinyInteger(_) => quote! { ColumnType::TinyInteger.def() }, - ColumnType::SmallInteger(_) => quote! { ColumnType::SmallInteger.def() }, - ColumnType::Integer(_) => quote! { ColumnType::Integer.def() }, - ColumnType::BigInteger(_) => quote! { ColumnType::BigInteger.def() }, - ColumnType::TinyUnsigned(_) => quote! { ColumnType::TinyUnsigned.def() }, - ColumnType::SmallUnsigned(_) => quote! { ColumnType::SmallUnsigned.def() }, - ColumnType::Unsigned(_) => quote! { ColumnType::Unsigned.def() }, - ColumnType::BigUnsigned(_) => quote! { ColumnType::BigUnsigned.def() }, - ColumnType::Float(_) => quote! { ColumnType::Float.def() }, - ColumnType::Double(_) => quote! { ColumnType::Double.def() }, - ColumnType::Decimal(s) => match s { - Some((s1, s2)) => quote! { ColumnType::Decimal(Some((#s1, #s2))).def() }, - None => quote! { ColumnType::Decimal(None).def() }, - }, - ColumnType::DateTime(_) => quote! { ColumnType::DateTime.def() }, - ColumnType::Timestamp(_) => quote! { ColumnType::Timestamp.def() }, - ColumnType::TimestampWithTimeZone(_) => { - quote! { ColumnType::TimestampWithTimeZone.def() } + fn write_col_def(col_type: &ColumnType) -> TokenStream { + match col_type { + ColumnType::Char(s) => match s { + Some(s) => quote! { ColumnType::Char(Some(#s)) }, + None => quote! { ColumnType::Char(None) }, + }, + ColumnType::String(s) => match s { + Some(s) => quote! { ColumnType::String(Some(#s)) }, + None => quote! { ColumnType::String(None) }, + }, + ColumnType::Text => quote! { ColumnType::Text }, + ColumnType::TinyInteger(_) => quote! { ColumnType::TinyInteger }, + ColumnType::SmallInteger(_) => quote! { ColumnType::SmallInteger }, + ColumnType::Integer(_) => quote! { ColumnType::Integer }, + ColumnType::BigInteger(_) => quote! { ColumnType::BigInteger }, + ColumnType::TinyUnsigned(_) => quote! { ColumnType::TinyUnsigned }, + ColumnType::SmallUnsigned(_) => quote! { ColumnType::SmallUnsigned }, + ColumnType::Unsigned(_) => quote! { ColumnType::Unsigned }, + ColumnType::BigUnsigned(_) => quote! { ColumnType::BigUnsigned }, + ColumnType::Float(_) => quote! { ColumnType::Float }, + ColumnType::Double(_) => quote! { ColumnType::Double }, + ColumnType::Decimal(s) => match s { + Some((s1, s2)) => quote! { ColumnType::Decimal(Some((#s1, #s2))) }, + None => quote! { ColumnType::Decimal(None) }, + }, + ColumnType::DateTime(_) => quote! { ColumnType::DateTime }, + ColumnType::Timestamp(_) => quote! { ColumnType::Timestamp }, + ColumnType::TimestampWithTimeZone(_) => { + quote! { ColumnType::TimestampWithTimeZone } + } + ColumnType::Time(_) => quote! { ColumnType::Time }, + ColumnType::Date => quote! { ColumnType::Date }, + ColumnType::Binary(BlobSize::Blob(_)) | ColumnType::VarBinary(_) => { + quote! { ColumnType::Binary } + } + ColumnType::Binary(BlobSize::Tiny) => quote! { ColumnType::TinyBinary }, + ColumnType::Binary(BlobSize::Medium) => quote! { ColumnType::MediumBinary }, + ColumnType::Binary(BlobSize::Long) => quote! { ColumnType::LongBinary }, + ColumnType::Boolean => quote! { ColumnType::Boolean }, + ColumnType::Money(s) => match s { + Some((s1, s2)) => quote! { ColumnType::Money(Some((#s1, #s2))) }, + None => quote! { ColumnType::Money(None) }, + }, + ColumnType::Json => quote! { ColumnType::Json }, + ColumnType::JsonBinary => quote! { ColumnType::JsonBinary }, + ColumnType::Uuid => quote! { ColumnType::Uuid }, + ColumnType::Custom(s) => { + let s = s.to_string(); + quote! { ColumnType::Custom(#s.to_owned()) } + } + ColumnType::Enum { name, .. } => { + let enum_ident = format_ident!("{}", name.to_string().to_camel_case()); + quote! { #enum_ident::db_type() } + } + ColumnType::Array(column_type) => { + let column_type = write_col_def(column_type); + quote! { ColumnType::Array(sea_orm::sea_query::SeaRc::new(#column_type)) } + } + #[allow(unreachable_patterns)] + _ => unimplemented!(), } - ColumnType::Time(_) => quote! { ColumnType::Time.def() }, - ColumnType::Date => quote! { ColumnType::Date.def() }, - ColumnType::Binary(BlobSize::Blob(_)) | ColumnType::VarBinary(_) => { - quote! { ColumnType::Binary.def() } - } - ColumnType::Binary(BlobSize::Tiny) => quote! { ColumnType::TinyBinary.def() }, - ColumnType::Binary(BlobSize::Medium) => quote! { ColumnType::MediumBinary.def() }, - ColumnType::Binary(BlobSize::Long) => quote! { ColumnType::LongBinary.def() }, - ColumnType::Boolean => quote! { ColumnType::Boolean.def() }, - ColumnType::Money(s) => match s { - Some((s1, s2)) => quote! { ColumnType::Money(Some((#s1, #s2))).def() }, - None => quote! { ColumnType::Money(None).def() }, - }, - ColumnType::Json => quote! { ColumnType::Json.def() }, - ColumnType::JsonBinary => quote! { ColumnType::JsonBinary.def() }, - ColumnType::Uuid => quote! { ColumnType::Uuid.def() }, - ColumnType::Custom(s) => { - let s = s.to_string(); - quote! { ColumnType::Custom(#s.to_owned()).def() } - } - ColumnType::Enum { name, .. } => { - let enum_ident = format_ident!("{}", name.to_string().to_camel_case()); - quote! { #enum_ident::db_type() } - } - #[allow(unreachable_patterns)] - _ => unimplemented!(), - }; + } + let mut col_def = write_col_def(&self.col_type); + col_def.extend(quote! { + .def() + }); if !self.not_null { col_def.extend(quote! { .null() diff --git a/sea-orm-codegen/src/entity/writer.rs b/sea-orm-codegen/src/entity/writer.rs index 1c63af09..3eea9056 100644 --- a/sea-orm-codegen/src/entity/writer.rs +++ b/sea-orm-codegen/src/entity/writer.rs @@ -672,7 +672,7 @@ mod tests { }; use pretty_assertions::assert_eq; use proc_macro2::TokenStream; - use sea_query::{ColumnType, ForeignKeyAction}; + use sea_query::{ColumnType, ForeignKeyAction, SeaRc}; use std::io::{self, BufRead, BufReader, Read}; fn setup() -> Vec { @@ -1120,6 +1120,41 @@ mod tests { name: "id".to_owned(), }], }, + Entity { + table_name: "collection".to_owned(), + columns: vec![ + Column { + name: "id".to_owned(), + col_type: ColumnType::Integer(Some(11)), + auto_increment: true, + not_null: true, + unique: false, + }, + Column { + name: "integers".to_owned(), + col_type: ColumnType::Array(SeaRc::new(Box::new(ColumnType::Integer( + None, + )))), + auto_increment: false, + not_null: true, + unique: false, + }, + Column { + name: "integers_opt".to_owned(), + col_type: ColumnType::Array(SeaRc::new(Box::new(ColumnType::Integer( + None, + )))), + auto_increment: false, + not_null: false, + unique: false, + }, + ], + relations: vec![], + conjunct_relations: vec![], + primary_keys: vec![PrimaryKey { + name: "id".to_owned(), + }], + }, ] } @@ -1144,7 +1179,7 @@ mod tests { #[test] fn test_gen_expanded_code_blocks() -> io::Result<()> { let entities = setup(); - const ENTITY_FILES: [&str; 8] = [ + const ENTITY_FILES: [&str; 9] = [ include_str!("../../tests/expanded/cake.rs"), include_str!("../../tests/expanded/cake_filling.rs"), include_str!("../../tests/expanded/filling.rs"), @@ -1153,8 +1188,9 @@ mod tests { include_str!("../../tests/expanded/rust_keyword.rs"), include_str!("../../tests/expanded/cake_with_float.rs"), include_str!("../../tests/expanded/cake_with_double.rs"), + include_str!("../../tests/expanded/collection.rs"), ]; - const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 8] = [ + const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 9] = [ include_str!("../../tests/expanded_with_schema_name/cake.rs"), include_str!("../../tests/expanded_with_schema_name/cake_filling.rs"), include_str!("../../tests/expanded_with_schema_name/filling.rs"), @@ -1163,6 +1199,7 @@ mod tests { include_str!("../../tests/expanded_with_schema_name/rust_keyword.rs"), include_str!("../../tests/expanded_with_schema_name/cake_with_float.rs"), include_str!("../../tests/expanded_with_schema_name/cake_with_double.rs"), + include_str!("../../tests/expanded_with_schema_name/collection.rs"), ]; assert_eq!(entities.len(), ENTITY_FILES.len()); @@ -1224,7 +1261,7 @@ mod tests { #[test] fn test_gen_compact_code_blocks() -> io::Result<()> { let entities = setup(); - const ENTITY_FILES: [&str; 8] = [ + const ENTITY_FILES: [&str; 9] = [ include_str!("../../tests/compact/cake.rs"), include_str!("../../tests/compact/cake_filling.rs"), include_str!("../../tests/compact/filling.rs"), @@ -1233,8 +1270,9 @@ mod tests { include_str!("../../tests/compact/rust_keyword.rs"), include_str!("../../tests/compact/cake_with_float.rs"), include_str!("../../tests/compact/cake_with_double.rs"), + include_str!("../../tests/compact/collection.rs"), ]; - const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 8] = [ + const ENTITY_FILES_WITH_SCHEMA_NAME: [&str; 9] = [ include_str!("../../tests/compact_with_schema_name/cake.rs"), include_str!("../../tests/compact_with_schema_name/cake_filling.rs"), include_str!("../../tests/compact_with_schema_name/filling.rs"), @@ -1243,6 +1281,7 @@ mod tests { include_str!("../../tests/compact_with_schema_name/rust_keyword.rs"), include_str!("../../tests/compact_with_schema_name/cake_with_float.rs"), include_str!("../../tests/compact_with_schema_name/cake_with_double.rs"), + include_str!("../../tests/compact_with_schema_name/collection.rs"), ]; assert_eq!(entities.len(), ENTITY_FILES.len()); diff --git a/sea-orm-codegen/tests/compact/collection.rs b/sea-orm-codegen/tests/compact/collection.rs new file mode 100644 index 00000000..e272ac31 --- /dev/null +++ b/sea-orm-codegen/tests/compact/collection.rs @@ -0,0 +1,17 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "collection")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub integers: Vec , + pub integers_opt: Option > , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/compact_with_schema_name/collection.rs b/sea-orm-codegen/tests/compact_with_schema_name/collection.rs new file mode 100644 index 00000000..5fe8d73c --- /dev/null +++ b/sea-orm-codegen/tests/compact_with_schema_name/collection.rs @@ -0,0 +1,17 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(schema_name = "schema_name", table_name = "collection")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub integers: Vec , + pub integers_opt: Option > , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/expanded/collection.rs b/sea-orm-codegen/tests/expanded/collection.rs new file mode 100644 index 00000000..1414dfc4 --- /dev/null +++ b/sea-orm-codegen/tests/expanded/collection.rs @@ -0,0 +1,60 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn table_name(&self) -> &str { + "collection" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] +pub struct Model { + pub id: i32, + pub integers: Vec , + pub integers_opt: Option > , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Integers, + IntegersOpt, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Integers => ColumnType::Array(sea_orm::sea_query::SeaRc::new(ColumnType::Integer)).def(), + Self::IntegersOpt => ColumnType::Array(sea_orm::sea_query::SeaRc::new(ColumnType::Integer)).def().null(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!("No RelationDef") + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/sea-orm-codegen/tests/expanded_with_schema_name/collection.rs b/sea-orm-codegen/tests/expanded_with_schema_name/collection.rs new file mode 100644 index 00000000..b7b1cb8b --- /dev/null +++ b/sea-orm-codegen/tests/expanded_with_schema_name/collection.rs @@ -0,0 +1,64 @@ +//! SeaORM Entity. Generated by sea-orm-codegen 0.10.0 + +use sea_orm::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +pub struct Entity; + +impl EntityName for Entity { + fn schema_name(&self) -> Option< &str > { + Some("schema_name") + } + + fn table_name(&self) -> &str { + "collection" + } +} + +#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel, Eq)] +pub struct Model { + pub id: i32, + pub integers: Vec , + pub integers_opt: Option > , +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Integers, + IntegersOpt, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +impl PrimaryKeyTrait for PrimaryKey { + type ValueType = i32; + fn auto_increment() -> bool { + true + } +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl ColumnTrait for Column { + type EntityName = Entity; + fn def(&self) -> ColumnDef { + match self { + Self::Id => ColumnType::Integer.def(), + Self::Integers => ColumnType::Array(sea_orm::sea_query::SeaRc::new(ColumnType::Integer)).def(), + Self::IntegersOpt => ColumnType::Array(sea_orm::sea_query::SeaRc::new(ColumnType::Integer)).def().null(), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!("No RelationDef") + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/src/database/mock.rs b/src/database/mock.rs index 1677938f..69b868e1 100644 --- a/src/database/mock.rs +++ b/src/database/mock.rs @@ -711,4 +711,79 @@ mod tests { Ok(()) } + + #[cfg(feature = "postgres-array")] + #[smol_potat::test] + async fn test_postgres_array_1() -> Result<(), DbErr> { + mod collection { + use crate as sea_orm; + use crate::entity::prelude::*; + + #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] + #[sea_orm(table_name = "collection")] + pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub integers: Vec, + pub integers_opt: Option>, + } + + #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] + pub enum Relation {} + + impl ActiveModelBehavior for ActiveModel {} + } + + let db = MockDatabase::new(DbBackend::Postgres) + .append_query_results(vec![vec![ + collection::Model { + id: 1, + integers: vec![1, 2, 3], + integers_opt: Some(vec![1, 2, 3]), + }, + collection::Model { + id: 2, + integers: vec![], + integers_opt: Some(vec![]), + }, + collection::Model { + id: 3, + integers: vec![3, 1, 4], + integers_opt: None, + }, + ]]) + .into_connection(); + + assert_eq!( + collection::Entity::find().all(&db).await?, + vec![ + collection::Model { + id: 1, + integers: vec![1, 2, 3], + integers_opt: Some(vec![1, 2, 3]), + }, + collection::Model { + id: 2, + integers: vec![], + integers_opt: Some(vec![]), + }, + collection::Model { + id: 3, + integers: vec![3, 1, 4], + integers_opt: None, + }, + ] + ); + + assert_eq!( + db.into_transaction_log(), + vec![Transaction::from_sql_and_values( + DbBackend::Postgres, + r#"SELECT "collection"."id", "collection"."integers", "collection"."integers_opt" FROM "collection""#, + vec![] + ),] + ); + + Ok(()) + } } diff --git a/src/entity/column.rs b/src/entity/column.rs index 802a3de2..81f7215f 100644 --- a/src/entity/column.rs +++ b/src/entity/column.rs @@ -84,6 +84,8 @@ pub enum ColumnType { /// Variants of enum variants: Vec, }, + /// Array of a specific data type (PostgreSQL only) + Array(SeaRc), } impl PartialEq for ColumnType { @@ -398,80 +400,111 @@ impl ColumnDef { } impl From for sea_query::ColumnType { - fn from(col: ColumnType) -> Self { - match col { - ColumnType::Char(s) => sea_query::ColumnType::Char(s), - ColumnType::String(s) => sea_query::ColumnType::String(s), - ColumnType::Text => sea_query::ColumnType::Text, - ColumnType::TinyInteger => sea_query::ColumnType::TinyInteger(None), - ColumnType::SmallInteger => sea_query::ColumnType::SmallInteger(None), - ColumnType::Integer => sea_query::ColumnType::Integer(None), - ColumnType::BigInteger => sea_query::ColumnType::BigInteger(None), - ColumnType::TinyUnsigned => sea_query::ColumnType::TinyUnsigned(None), - ColumnType::SmallUnsigned => sea_query::ColumnType::SmallUnsigned(None), - ColumnType::Unsigned => sea_query::ColumnType::Unsigned(None), - ColumnType::BigUnsigned => sea_query::ColumnType::BigUnsigned(None), - ColumnType::Float => sea_query::ColumnType::Float(None), - ColumnType::Double => sea_query::ColumnType::Double(None), - ColumnType::Decimal(s) => sea_query::ColumnType::Decimal(s), - ColumnType::DateTime => sea_query::ColumnType::DateTime(None), - ColumnType::Timestamp => sea_query::ColumnType::Timestamp(None), - ColumnType::TimestampWithTimeZone => sea_query::ColumnType::TimestampWithTimeZone(None), - ColumnType::Time => sea_query::ColumnType::Time(None), - ColumnType::Date => sea_query::ColumnType::Date, - ColumnType::Binary => sea_query::ColumnType::Binary(sea_query::BlobSize::Blob(None)), - ColumnType::TinyBinary => sea_query::ColumnType::Binary(sea_query::BlobSize::Tiny), - ColumnType::MediumBinary => sea_query::ColumnType::Binary(sea_query::BlobSize::Medium), - ColumnType::LongBinary => sea_query::ColumnType::Binary(sea_query::BlobSize::Long), - ColumnType::Boolean => sea_query::ColumnType::Boolean, - ColumnType::Money(s) => sea_query::ColumnType::Money(s), - ColumnType::Json => sea_query::ColumnType::Json, - ColumnType::JsonBinary => sea_query::ColumnType::JsonBinary, - ColumnType::Custom(s) => { - sea_query::ColumnType::Custom(sea_query::SeaRc::new(sea_query::Alias::new(&s))) + fn from(column_type: ColumnType) -> Self { + fn convert_column_type(column_type: &ColumnType) -> sea_query::ColumnType { + match column_type { + ColumnType::Char(s) => sea_query::ColumnType::Char(*s), + ColumnType::String(s) => sea_query::ColumnType::String(*s), + ColumnType::Text => sea_query::ColumnType::Text, + ColumnType::TinyInteger => sea_query::ColumnType::TinyInteger(None), + ColumnType::SmallInteger => sea_query::ColumnType::SmallInteger(None), + ColumnType::Integer => sea_query::ColumnType::Integer(None), + ColumnType::BigInteger => sea_query::ColumnType::BigInteger(None), + ColumnType::TinyUnsigned => sea_query::ColumnType::TinyUnsigned(None), + ColumnType::SmallUnsigned => sea_query::ColumnType::SmallUnsigned(None), + ColumnType::Unsigned => sea_query::ColumnType::Unsigned(None), + ColumnType::BigUnsigned => sea_query::ColumnType::BigUnsigned(None), + ColumnType::Float => sea_query::ColumnType::Float(None), + ColumnType::Double => sea_query::ColumnType::Double(None), + ColumnType::Decimal(s) => sea_query::ColumnType::Decimal(*s), + ColumnType::DateTime => sea_query::ColumnType::DateTime(None), + ColumnType::Timestamp => sea_query::ColumnType::Timestamp(None), + ColumnType::TimestampWithTimeZone => { + sea_query::ColumnType::TimestampWithTimeZone(None) + } + ColumnType::Time => sea_query::ColumnType::Time(None), + ColumnType::Date => sea_query::ColumnType::Date, + ColumnType::Binary => { + sea_query::ColumnType::Binary(sea_query::BlobSize::Blob(None)) + } + ColumnType::TinyBinary => sea_query::ColumnType::Binary(sea_query::BlobSize::Tiny), + ColumnType::MediumBinary => { + sea_query::ColumnType::Binary(sea_query::BlobSize::Medium) + } + ColumnType::LongBinary => sea_query::ColumnType::Binary(sea_query::BlobSize::Long), + ColumnType::Boolean => sea_query::ColumnType::Boolean, + ColumnType::Money(s) => sea_query::ColumnType::Money(*s), + ColumnType::Json => sea_query::ColumnType::Json, + ColumnType::JsonBinary => sea_query::ColumnType::JsonBinary, + ColumnType::Custom(s) => { + sea_query::ColumnType::Custom(sea_query::SeaRc::new(sea_query::Alias::new(s))) + } + ColumnType::Uuid => sea_query::ColumnType::Uuid, + ColumnType::Enum { name, variants } => sea_query::ColumnType::Enum { + name: SeaRc::clone(name), + variants: variants.clone(), + }, + ColumnType::Array(column_type) => { + let column_type = convert_column_type(column_type); + sea_query::ColumnType::Array(SeaRc::new(Box::new(column_type))) + } } - ColumnType::Uuid => sea_query::ColumnType::Uuid, - ColumnType::Enum { name, variants } => sea_query::ColumnType::Enum { name, variants }, } + convert_column_type(&column_type) } } impl From for ColumnType { - fn from(col_type: sea_query::ColumnType) -> Self { - #[allow(unreachable_patterns)] - match col_type { - sea_query::ColumnType::Char(s) => Self::Char(s), - sea_query::ColumnType::String(s) => Self::String(s), - sea_query::ColumnType::Text => Self::Text, - sea_query::ColumnType::TinyInteger(_) => Self::TinyInteger, - sea_query::ColumnType::SmallInteger(_) => Self::SmallInteger, - sea_query::ColumnType::Integer(_) => Self::Integer, - sea_query::ColumnType::BigInteger(_) => Self::BigInteger, - sea_query::ColumnType::TinyUnsigned(_) => Self::TinyUnsigned, - sea_query::ColumnType::SmallUnsigned(_) => Self::SmallUnsigned, - sea_query::ColumnType::Unsigned(_) => Self::Unsigned, - sea_query::ColumnType::BigUnsigned(_) => Self::BigUnsigned, - sea_query::ColumnType::Float(_) => Self::Float, - sea_query::ColumnType::Double(_) => Self::Double, - sea_query::ColumnType::Decimal(s) => Self::Decimal(s), - sea_query::ColumnType::DateTime(_) => Self::DateTime, - sea_query::ColumnType::Timestamp(_) => Self::Timestamp, - sea_query::ColumnType::TimestampWithTimeZone(_) => Self::TimestampWithTimeZone, - sea_query::ColumnType::Time(_) => Self::Time, - sea_query::ColumnType::Date => Self::Date, - sea_query::ColumnType::Binary(sea_query::BlobSize::Blob(_)) => Self::Binary, - sea_query::ColumnType::Binary(sea_query::BlobSize::Tiny) => Self::TinyBinary, - sea_query::ColumnType::Binary(sea_query::BlobSize::Medium) => Self::MediumBinary, - sea_query::ColumnType::Binary(sea_query::BlobSize::Long) => Self::LongBinary, - sea_query::ColumnType::Boolean => Self::Boolean, - sea_query::ColumnType::Money(s) => Self::Money(s), - sea_query::ColumnType::Json => Self::Json, - sea_query::ColumnType::JsonBinary => Self::JsonBinary, - sea_query::ColumnType::Custom(s) => Self::Custom(s.to_string()), - sea_query::ColumnType::Uuid => Self::Uuid, - sea_query::ColumnType::Enum { name, variants } => Self::Enum { name, variants }, - _ => unimplemented!(), + fn from(column_type: sea_query::ColumnType) -> Self { + #[allow(clippy::redundant_allocation)] + fn convert_column_type(column_type: &sea_query::ColumnType) -> ColumnType { + #[allow(unreachable_patterns)] + match column_type { + sea_query::ColumnType::Char(s) => ColumnType::Char(*s), + sea_query::ColumnType::String(s) => ColumnType::String(*s), + sea_query::ColumnType::Text => ColumnType::Text, + sea_query::ColumnType::TinyInteger(_) => ColumnType::TinyInteger, + sea_query::ColumnType::SmallInteger(_) => ColumnType::SmallInteger, + sea_query::ColumnType::Integer(_) => ColumnType::Integer, + sea_query::ColumnType::BigInteger(_) => ColumnType::BigInteger, + sea_query::ColumnType::TinyUnsigned(_) => ColumnType::TinyUnsigned, + sea_query::ColumnType::SmallUnsigned(_) => ColumnType::SmallUnsigned, + sea_query::ColumnType::Unsigned(_) => ColumnType::Unsigned, + sea_query::ColumnType::BigUnsigned(_) => ColumnType::BigUnsigned, + sea_query::ColumnType::Float(_) => ColumnType::Float, + sea_query::ColumnType::Double(_) => ColumnType::Double, + sea_query::ColumnType::Decimal(s) => ColumnType::Decimal(*s), + sea_query::ColumnType::DateTime(_) => ColumnType::DateTime, + sea_query::ColumnType::Timestamp(_) => ColumnType::Timestamp, + sea_query::ColumnType::TimestampWithTimeZone(_) => { + ColumnType::TimestampWithTimeZone + } + sea_query::ColumnType::Time(_) => ColumnType::Time, + sea_query::ColumnType::Date => ColumnType::Date, + sea_query::ColumnType::Binary(sea_query::BlobSize::Blob(_)) => ColumnType::Binary, + sea_query::ColumnType::Binary(sea_query::BlobSize::Tiny) => ColumnType::TinyBinary, + sea_query::ColumnType::Binary(sea_query::BlobSize::Medium) => { + ColumnType::MediumBinary + } + sea_query::ColumnType::Binary(sea_query::BlobSize::Long) => ColumnType::LongBinary, + sea_query::ColumnType::Boolean => ColumnType::Boolean, + sea_query::ColumnType::Money(s) => ColumnType::Money(*s), + sea_query::ColumnType::Json => ColumnType::Json, + sea_query::ColumnType::JsonBinary => ColumnType::JsonBinary, + sea_query::ColumnType::Custom(s) => ColumnType::Custom(s.to_string()), + sea_query::ColumnType::Uuid => ColumnType::Uuid, + sea_query::ColumnType::Enum { name, variants } => ColumnType::Enum { + name: SeaRc::clone(name), + variants: variants.clone(), + }, + sea_query::ColumnType::Array(column_type) => { + let column_type = convert_column_type(column_type); + ColumnType::Array(SeaRc::new(column_type)) + } + _ => unimplemented!(), + } } + convert_column_type(&column_type) } } diff --git a/src/executor/query.rs b/src/executor/query.rs index 3b53542b..44d57013 100644 --- a/src/executor/query.rs +++ b/src/executor/query.rs @@ -444,6 +444,130 @@ impl TryGetable for u32 { } } +#[cfg(feature = "postgres-array")] +mod postgres_array { + use super::*; + + #[allow(unused_macros)] + macro_rules! try_getable_postgres_array { + ( $type: ty ) => { + impl TryGetable for Vec<$type> { + #[allow(unused_variables)] + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + let column = format!("{}{}", pre, col); + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + panic!("{} unsupported by sqlx-mysql", stringify!($type)) + } + #[cfg(feature = "sqlx-postgres")] + QueryResultRow::SqlxPostgres(row) => { + use sqlx::Row; + row.try_get::>, _>(column.as_str()) + .map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e))) + .and_then(|opt| opt.ok_or(TryGetError::Null(column))) + } + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(_) => { + panic!("{} unsupported by sqlx-sqlite", stringify!($type)) + } + #[cfg(feature = "mock")] + QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + TryGetError::Null(column) + }), + #[allow(unreachable_patterns)] + _ => unreachable!(), + } + } + } + }; + } + + try_getable_postgres_array!(bool); + try_getable_postgres_array!(i8); + try_getable_postgres_array!(i16); + try_getable_postgres_array!(i32); + try_getable_postgres_array!(i64); + try_getable_postgres_array!(f32); + try_getable_postgres_array!(f64); + try_getable_postgres_array!(String); + + #[cfg(feature = "with-json")] + try_getable_postgres_array!(serde_json::Value); + + #[cfg(feature = "with-chrono")] + try_getable_postgres_array!(chrono::NaiveDate); + + #[cfg(feature = "with-chrono")] + try_getable_postgres_array!(chrono::NaiveTime); + + #[cfg(feature = "with-chrono")] + try_getable_postgres_array!(chrono::NaiveDateTime); + + #[cfg(feature = "with-chrono")] + try_getable_postgres_array!(chrono::DateTime); + + #[cfg(feature = "with-chrono")] + try_getable_postgres_array!(chrono::DateTime); + + #[cfg(feature = "with-chrono")] + try_getable_postgres_array!(chrono::DateTime); + + #[cfg(feature = "with-time")] + try_getable_postgres_array!(time::Date); + + #[cfg(feature = "with-time")] + try_getable_postgres_array!(time::Time); + + #[cfg(feature = "with-time")] + try_getable_postgres_array!(time::PrimitiveDateTime); + + #[cfg(feature = "with-time")] + try_getable_postgres_array!(time::OffsetDateTime); + + #[cfg(feature = "with-rust_decimal")] + try_getable_postgres_array!(rust_decimal::Decimal); + + #[cfg(feature = "with-uuid")] + try_getable_postgres_array!(uuid::Uuid); + + impl TryGetable for Vec { + #[allow(unused_variables)] + fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result { + let column = format!("{}{}", pre, col); + match &res.row { + #[cfg(feature = "sqlx-mysql")] + QueryResultRow::SqlxMySql(row) => { + panic!("{} unsupported by sqlx-mysql", stringify!($type)) + } + #[cfg(feature = "sqlx-postgres")] + QueryResultRow::SqlxPostgres(row) => { + use sqlx::postgres::types::Oid; + // Since 0.6.0, SQLx has dropped direct mapping from PostgreSQL's OID to Rust's `u32`; + // Instead, `u32` was wrapped by a `sqlx::Oid`. + use sqlx::Row; + row.try_get::>, _>(column.as_str()) + .map_err(|e| TryGetError::DbErr(crate::sqlx_error_to_query_err(e))) + .and_then(|opt| opt.ok_or(TryGetError::Null(column))) + .map(|oids| oids.into_iter().map(|oid| oid.0).collect()) + } + #[cfg(feature = "sqlx-sqlite")] + QueryResultRow::SqlxSqlite(_) => { + panic!("{} unsupported by sqlx-sqlite", stringify!($type)) + } + #[cfg(feature = "mock")] + QueryResultRow::Mock(row) => row.try_get(column.as_str()).map_err(|e| { + debug_print!("{:#?}", e.to_string()); + TryGetError::Null(column) + }), + #[allow(unreachable_patterns)] + _ => unreachable!(), + } + } + } +} + // TryGetableMany // /// Perform a query on multiple columns diff --git a/tests/byte_primary_key_tests.rs b/tests/byte_primary_key_tests.rs index 2f66a13a..0ed5e5a6 100644 --- a/tests/byte_primary_key_tests.rs +++ b/tests/byte_primary_key_tests.rs @@ -41,7 +41,7 @@ pub async fn create_and_update(db: &DatabaseConnection) -> Result<(), DbErr> { }; let update_res = Entity::update(updated_active_model.clone()) - .filter(Column::Id.eq(vec![1, 2, 4])) + .filter(Column::Id.eq(vec![1_u8, 2_u8, 4_u8])) // annotate it as Vec explicitly .exec(db) .await; @@ -53,7 +53,7 @@ pub async fn create_and_update(db: &DatabaseConnection) -> Result<(), DbErr> { ); let update_res = Entity::update(updated_active_model) - .filter(Column::Id.eq(vec![1, 2, 3])) + .filter(Column::Id.eq(vec![1_u8, 2_u8, 3_u8])) // annotate it as Vec explicitly .exec(db) .await?; @@ -67,7 +67,7 @@ pub async fn create_and_update(db: &DatabaseConnection) -> Result<(), DbErr> { assert_eq!( Entity::find() - .filter(Column::Id.eq(vec![1, 2, 3])) + .filter(Column::Id.eq(vec![1_u8, 2_u8, 3_u8])) // annotate it as Vec explicitly .one(db) .await?, Some(Model { diff --git a/tests/collection_tests.rs b/tests/collection_tests.rs new file mode 100644 index 00000000..447a9f90 --- /dev/null +++ b/tests/collection_tests.rs @@ -0,0 +1,95 @@ +pub mod common; + +pub use common::{features::*, setup::*, TestContext}; +use pretty_assertions::assert_eq; +use sea_orm::{entity::prelude::*, entity::*, DatabaseConnection}; + +#[sea_orm_macros::test] +#[cfg(all(feature = "sqlx-postgres", feature = "postgres-array"))] +async fn main() -> Result<(), DbErr> { + let ctx = TestContext::new("collection_tests").await; + create_tables(&ctx.db).await?; + insert_collection(&ctx.db).await?; + update_collection(&ctx.db).await?; + ctx.delete().await; + + Ok(()) +} + +pub async fn insert_collection(db: &DatabaseConnection) -> Result<(), DbErr> { + use collection::*; + + assert_eq!( + Model { + id: 1, + integers: vec![1, 2, 3], + integers_opt: Some(vec![1, 2, 3]), + } + .into_active_model() + .insert(db) + .await?, + Model { + id: 1, + integers: vec![1, 2, 3], + integers_opt: Some(vec![1, 2, 3]), + } + ); + + assert_eq!( + Model { + id: 2, + integers: vec![10, 9], + integers_opt: None, + } + .into_active_model() + .insert(db) + .await?, + Model { + id: 2, + integers: vec![10, 9], + integers_opt: None, + } + ); + + assert_eq!( + Model { + id: 3, + integers: vec![], + integers_opt: Some(vec![]), + } + .into_active_model() + .insert(db) + .await?, + Model { + id: 3, + integers: vec![], + integers_opt: Some(vec![]), + } + ); + + Ok(()) +} + +pub async fn update_collection(db: &DatabaseConnection) -> Result<(), DbErr> { + use collection::*; + + let model = Entity::find_by_id(1).one(db).await?.unwrap(); + + ActiveModel { + integers: Set(vec![4, 5, 6]), + integers_opt: Set(Some(vec![4, 5, 6])), + ..model.into_active_model() + } + .update(db) + .await?; + + ActiveModel { + id: Unchanged(3), + integers: Set(vec![3, 1, 4]), + integers_opt: Set(None), + } + .update(db) + .await?; + + Ok(()) +} diff --git a/tests/common/features/collection.rs b/tests/common/features/collection.rs new file mode 100644 index 00000000..898cadeb --- /dev/null +++ b/tests/common/features/collection.rs @@ -0,0 +1,15 @@ +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "collection")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub integers: Vec, + pub integers_opt: Option>, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/tests/common/features/mod.rs b/tests/common/features/mod.rs index ce95af17..1e90f5c7 100644 --- a/tests/common/features/mod.rs +++ b/tests/common/features/mod.rs @@ -2,6 +2,7 @@ pub mod active_enum; pub mod active_enum_child; pub mod applog; pub mod byte_primary_key; +pub mod collection; pub mod custom_active_model; pub mod insert_default; pub mod json_struct; @@ -18,6 +19,7 @@ pub use active_enum::Entity as ActiveEnum; pub use active_enum_child::Entity as ActiveEnumChild; pub use applog::Entity as Applog; pub use byte_primary_key::Entity as BytePrimaryKey; +pub use collection::Entity as Collection; pub use insert_default::Entity as InsertDefault; pub use json_struct::Entity as JsonStruct; pub use json_vec::Entity as JsonVec; diff --git a/tests/common/features/schema.rs b/tests/common/features/schema.rs index f14568bc..e6dc2b49 100644 --- a/tests/common/features/schema.rs +++ b/tests/common/features/schema.rs @@ -42,6 +42,10 @@ pub async fn create_tables(db: &DatabaseConnection) -> Result<(), DbErr> { create_active_enum_child_table(db).await?; create_insert_default_table(db).await?; + if DbBackend::Postgres == db_backend { + create_collection_table(db).await?; + } + Ok(()) } @@ -326,3 +330,27 @@ pub async fn create_json_struct_table(db: &DbConn) -> Result create_table(db, &stmt, JsonStruct).await } + +pub async fn create_collection_table(db: &DbConn) -> Result { + let stmt = sea_query::Table::create() + .table(collection::Entity) + .col( + ColumnDef::new(collection::Column::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col( + ColumnDef::new(collection::Column::Integers) + .array(sea_query::ColumnType::Integer(None)) + .not_null(), + ) + .col( + ColumnDef::new(collection::Column::IntegersOpt) + .array(sea_query::ColumnType::Integer(None)), + ) + .to_owned(); + + create_table(db, &stmt, Collection).await +}