diff --git a/examples/postgres/src/main.rs b/examples/postgres/src/main.rs index 7d8799c99..a48a80e5d 100644 --- a/examples/postgres/src/main.rs +++ b/examples/postgres/src/main.rs @@ -1,7 +1,8 @@ use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime}; +use postgres::types::IsNull::No; use postgres::{Client, NoTls, Row}; use rust_decimal::Decimal; -use sea_query::{ColumnDef, Iden, Order, PostgresQueryBuilder, Query, Table}; +use sea_query::{ColumnDef, ColumnType, Iden, Order, PostgresQueryBuilder, Query, Table}; use sea_query_postgres::PostgresBinder; use time::{ macros::{date, offset, time}, @@ -34,7 +35,7 @@ fn main() { .col(ColumnDef::new(Document::Timestamp).timestamp()) .col(ColumnDef::new(Document::TimestampWithTimeZone).timestamp_with_time_zone()) .col(ColumnDef::new(Document::Decimal).decimal()) - .col(ColumnDef::new(Document::Array).array("integer".into())) + .col(ColumnDef::new(Document::Array).array(ColumnType::Integer(None))) .build(PostgresQueryBuilder), ] .join("; "); diff --git a/sea-query-binder/Cargo.toml b/sea-query-binder/Cargo.toml index 5e895b46d..fd46f7622 100644 --- a/sea-query-binder/Cargo.toml +++ b/sea-query-binder/Cargo.toml @@ -17,22 +17,33 @@ rust-version = "1.60" [lib] [dependencies] -sea-query = { version = "^0", path = ".." } +sea-query = { version = "^0", path = "..", features = ["thread-safe"] } sqlx = { version = "^0.6.1", optional = true } +serde_json = { version = "^1", optional = true } +chrono = { version = "^0.4", default-features = false, features = ["clock"], optional = true } +postgres-types = { version = "^0", optional = true } +rust_decimal = { version = "^1", optional = true } +bigdecimal = { version = "^0.3", optional = true } +uuid = { version = "^1", optional = true } +proc-macro2 = { version = "1", optional = true } +quote = { version = "^1", optional = true } +time = { version = "^0.3", optional = true, features = ["macros", "formatting"] } +ipnetwork = { version = "^0.19", optional = true } +mac_address = { version = "^1.1", optional = true } [features] sqlx-mysql = ["sqlx/mysql"] sqlx-postgres = ["sqlx/postgres"] sqlx-sqlite = ["sqlx/sqlite"] sqlx-any = ["sqlx/any"] -with-chrono = ["sqlx/chrono", "sea-query/with-chrono"] -with-json = ["sqlx/json", "sea-query/with-json"] -with-rust_decimal = ["sqlx/decimal", "sea-query/with-rust_decimal"] -with-bigdecimal = ["sqlx/bigdecimal", "sea-query/with-bigdecimal"] -with-uuid = ["sqlx/uuid", "sea-query/with-uuid"] -with-time = ["sqlx/time", "sea-query/with-time"] -with-ipnetwork = ["sqlx/ipnetwork", "sea-query/with-ipnetwork"] -with-mac_address = ["sqlx/mac_address", "sea-query/with-mac_address"] +with-chrono = ["sqlx/chrono", "sea-query/with-chrono", "chrono"] +with-json = ["sqlx/json", "sea-query/with-json", "serde_json"] +with-rust_decimal = ["sqlx/decimal", "sea-query/with-rust_decimal", "rust_decimal"] +with-bigdecimal = ["sqlx/bigdecimal", "sea-query/with-bigdecimal", "bigdecimal"] +with-uuid = ["sqlx/uuid", "sea-query/with-uuid", "uuid"] +with-time = ["sqlx/time", "sea-query/with-time", "time"] +with-ipnetwork = ["sqlx/ipnetwork", "sea-query/with-ipnetwork", "ipnetwork"] +with-mac_address = ["sqlx/mac_address", "sea-query/with-mac_address", "mac_address"] postgres-array = ["sea-query/postgres-array"] runtime-async-std-native-tls = ["sqlx/runtime-async-std-native-tls"] runtime-async-std-rustls = ["sqlx/runtime-async-std-rustls", ] diff --git a/sea-query-binder/src/sqlx_any.rs b/sea-query-binder/src/sqlx_any.rs index 5c8fd3b65..bc03ec57d 100644 --- a/sea-query-binder/src/sqlx_any.rs +++ b/sea-query-binder/src/sqlx_any.rs @@ -114,7 +114,7 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::any::Any> for SqlxValues { panic!("SeaQuery doesn't support MacAddress arguments for Any"); } #[cfg(feature = "postgres-array")] - Value::Array(_) => { + Value::Array(_, _) => { panic!("SeaQuery doesn't support array arguments for Any"); } } diff --git a/sea-query-binder/src/sqlx_mysql.rs b/sea-query-binder/src/sqlx_mysql.rs index 991ce0133..460d7392b 100644 --- a/sea-query-binder/src/sqlx_mysql.rs +++ b/sea-query-binder/src/sqlx_mysql.rs @@ -106,7 +106,7 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::mysql::MySql> for SqlxValues { args.add(j.as_deref()); } #[cfg(feature = "postgres-array")] - Value::Array(_) => { + Value::Array(_, _) => { panic!("Mysql doesn't support array arguments"); } #[cfg(feature = "with-ipnetwork")] diff --git a/sea-query-binder/src/sqlx_postgres.rs b/sea-query-binder/src/sqlx_postgres.rs index f25c9806c..176ede156 100644 --- a/sea-query-binder/src/sqlx_postgres.rs +++ b/sea-query-binder/src/sqlx_postgres.rs @@ -1,5 +1,21 @@ +#[cfg(feature = "with-bigdecimal")] +use bigdecimal::BigDecimal; +#[cfg(feature = "with-chrono")] +use chrono::{DateTime, Local, NaiveDate, NaiveDateTime, NaiveTime, Utc}; +#[cfg(feature = "with-ipnetwork")] +use ipnetwork::IpNetwork; +#[cfg(feature = "with-mac_address")] +use mac_address::MacAddress; +#[cfg(feature = "with-rust_decimal")] +use rust_decimal::Decimal; +#[cfg(feature = "with-json")] +use serde_json::Value as Json; +#[cfg(feature = "with-uuid")] +use uuid::Uuid; + +use sea_query::{ArrayType, Value}; + use crate::SqlxValues; -use sea_query::Value; impl<'q> sqlx::IntoArguments<'q, sqlx::postgres::Postgres> for SqlxValues { fn into_arguments(self) -> sqlx::postgres::PgArguments { @@ -32,7 +48,7 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::postgres::Postgres> for SqlxValues { args.add(i.map(|i| i as i64)); } Value::BigUnsigned(i) => { - args.add(i.map(|i| >::try_from(i).unwrap())); + args.add(i.map(|i| >::try_from(i).unwrap())); } Value::Float(f) => { args.add(f); @@ -105,10 +121,6 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::postgres::Postgres> for SqlxValues { Value::Json(j) => { args.add(j.as_deref()); } - #[cfg(feature = "postgres-array")] - Value::Array(_) => { - panic!("SeaQuery doesn't support array arguments for Postgresql"); - } #[cfg(feature = "with-ipnetwork")] Value::IpNetwork(ip) => { args.add(ip.as_deref()); @@ -117,6 +129,191 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::postgres::Postgres> for SqlxValues { Value::MacAddress(mac) => { args.add(mac.as_deref()); } + #[cfg(feature = "postgres-array")] + Value::Array(ty, v) => match ty { + ArrayType::Bool => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Bool"); + args.add(value) + } + ArrayType::TinyInt => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::TinyInt"); + args.add(value) + } + ArrayType::SmallInt => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::SmallInt"); + args.add(value) + } + ArrayType::Int => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Int"); + args.add(value) + } + ArrayType::BigInt => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::BigInt"); + args.add(value) + } + ArrayType::TinyUnsigned => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::TinyUnsigned"); + let value: Option> = + value.map(|vec| vec.into_iter().map(|i| i as i16).collect()); + args.add(value) + } + ArrayType::SmallUnsigned => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::SmallUnsigned"); + let value: Option> = + value.map(|vec| vec.into_iter().map(|i| i as i32).collect()); + args.add(value) + } + ArrayType::Unsigned => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Unsigned"); + let value: Option> = + value.map(|vec| vec.into_iter().map(|i| i as i64).collect()); + args.add(value) + } + ArrayType::BigUnsigned => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::BigUnsigned"); + let value: Option> = value.map(|vec| { + vec.into_iter() + .map(|i| >::try_from(i).unwrap()) + .collect() + }); + args.add(value) + } + ArrayType::Float => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Float"); + args.add(value) + } + ArrayType::Double => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Double"); + args.add(value) + } + ArrayType::String => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::String"); + args.add(value) + } + ArrayType::Char => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Char"); + let value: Option> = + value.map(|vec| vec.into_iter().map(|c| c.to_string()).collect()); + args.add(value) + } + ArrayType::Bytes => { + let value: Option>> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Bytes"); + args.add(value) + } + #[cfg(feature = "with-chrono")] + ArrayType::ChronoDate => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::ChronoDate"); + args.add(value); + } + #[cfg(feature = "with-chrono")] + ArrayType::ChronoTime => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::ChronoTime"); + args.add(value); + } + #[cfg(feature = "with-chrono")] + ArrayType::ChronoDateTime => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::ChronoDateTime"); + args.add(value); + } + #[cfg(feature = "with-chrono")] + ArrayType::ChronoDateTimeUtc => { + let value: Option>> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::ChronoDateTimeUtc"); + args.add(value); + } + #[cfg(feature = "with-chrono")] + ArrayType::ChronoDateTimeLocal => { + let value: Option>> = Value::Array(ty, v).expect( + "This Value::Array should consist of Value::ChronoDateTimeLocal", + ); + args.add(value); + } + #[cfg(feature = "with-chrono")] + ArrayType::ChronoDateTimeWithTimeZone => { + let value: Option>> = Value::Array(ty, v).expect( + "This Value::Array should consist of Value::ChronoDateTimeWithTimeZone", + ); + args.add(value); + } + #[cfg(feature = "with-time")] + ArrayType::TimeDate => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::TimeDate"); + args.add(value); + } + #[cfg(feature = "with-time")] + ArrayType::TimeTime => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::TimeTime"); + args.add(value); + } + #[cfg(feature = "with-time")] + ArrayType::TimeDateTime => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::TimeDateTime"); + args.add(value); + } + #[cfg(feature = "with-time")] + ArrayType::TimeDateTimeWithTimeZone => { + let value: Option> = Value::Array(ty, v).expect( + "This Value::Array should consist of Value::TimeDateTimeWithTimeZone", + ); + args.add(value); + } + #[cfg(feature = "with-uuid")] + ArrayType::Uuid => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Uuid"); + args.add(value); + } + #[cfg(feature = "with-rust_decimal")] + ArrayType::Decimal => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Decimal"); + args.add(value); + } + #[cfg(feature = "with-bigdecimal")] + ArrayType::BigDecimal => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::BigDecimal"); + args.add(value); + } + #[cfg(feature = "with-json")] + ArrayType::Json => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Json"); + args.add(value); + } + #[cfg(feature = "with-ipnetwork")] + ArrayType::IpNetwork => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::IpNetwork"); + args.add(value); + } + #[cfg(feature = "with-mac_address")] + ArrayType::MacAddress => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::MacAddress"); + args.add(value); + } + }, } } args diff --git a/sea-query-binder/src/sqlx_sqlite.rs b/sea-query-binder/src/sqlx_sqlite.rs index 54d336b07..22747fc16 100644 --- a/sea-query-binder/src/sqlx_sqlite.rs +++ b/sea-query-binder/src/sqlx_sqlite.rs @@ -114,7 +114,7 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::sqlite::Sqlite> for SqlxValues { panic!("Sqlite doesn't support MacAddress arguments"); } #[cfg(feature = "postgres-array")] - Value::Array(_) => { + Value::Array(_, _) => { panic!("Sqlite doesn't support array arguments"); } } diff --git a/sea-query-postgres/src/lib.rs b/sea-query-postgres/src/lib.rs index 4ebffe656..782a29987 100644 --- a/sea-query-postgres/src/lib.rs +++ b/sea-query-postgres/src/lib.rs @@ -6,7 +6,7 @@ use postgres_types::{to_sql_checked, IsNull, ToSql, Type}; use sea_query::{query::*, QueryBuilder, Value}; #[derive(Clone, Debug, PartialEq)] -pub struct PostgresValue(pub sea_query::Value); +pub struct PostgresValue(pub Value); #[derive(Clone, Debug, PartialEq)] pub struct PostgresValues(pub Vec); @@ -101,13 +101,13 @@ impl ToSql for PostgresValue { #[cfg(feature = "with-uuid")] Value::Uuid(v) => v.as_deref().to_sql(ty, out), #[cfg(feature = "postgres-array")] - Value::Array(Some(v)) => v + Value::Array(_, Some(v)) => v .iter() .map(|v| PostgresValue(v.clone())) .collect::>() .to_sql(ty, out), #[cfg(feature = "postgres-array")] - Value::Array(None) => Ok(IsNull::Yes), + Value::Array(_, None) => Ok(IsNull::Yes), #[allow(unreachable_patterns)] _ => unimplemented!(), } diff --git a/sea-query-rusqlite/src/lib.rs b/sea-query-rusqlite/src/lib.rs index 86f18f571..6299bb183 100644 --- a/sea-query-rusqlite/src/lib.rs +++ b/sea-query-rusqlite/src/lib.rs @@ -126,7 +126,7 @@ impl ToSql for RusqliteValue { panic!("Rusqlite doesn't support MacAddress arguments"); } #[cfg(feature = "postgres-array")] - Value::Array(_) => { + Value::Array(_, _) => { panic!("Rusqlite doesn't support Array arguments"); } } diff --git a/src/backend/postgres/table.rs b/src/backend/postgres/table.rs index a175528c1..c6334f742 100644 --- a/src/backend/postgres/table.rs +++ b/src/backend/postgres/table.rs @@ -96,7 +96,11 @@ impl TableBuilder for PostgresQueryBuilder { ColumnType::Json => "json".into(), ColumnType::JsonBinary => "jsonb".into(), ColumnType::Uuid => "uuid".into(), - ColumnType::Array(elem_type) => format!("{}[]", elem_type.as_ref().unwrap()), + ColumnType::Array(elem_type) => { + let mut sql = String::new(); + self.prepare_column_type(elem_type, &mut sql); + format!("{}[]", sql) + } ColumnType::Custom(iden) => iden.to_string(), ColumnType::Enum { name, .. } => name.to_string(), ColumnType::Cidr => "cidr".into(), diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index bf7b8859a..ec62c9ac4 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -966,7 +966,7 @@ pub trait QueryBuilder: QuotedBuilder + EscapeBuilder + TableRefBuilder { #[cfg(feature = "with-mac_address")] Value::MacAddress(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "postgres-array")] - Value::Array(None) => write!(s, "NULL").unwrap(), + Value::Array(_, None) => write!(s, "NULL").unwrap(), Value::Bool(Some(b)) => write!(s, "{}", if *b { "TRUE" } else { "FALSE" }).unwrap(), Value::TinyInt(Some(v)) => write!(s, "{}", v).unwrap(), Value::SmallInt(Some(v)) => write!(s, "{}", v).unwrap(), @@ -1036,7 +1036,7 @@ pub trait QueryBuilder: QuotedBuilder + EscapeBuilder + TableRefBuilder { #[cfg(feature = "with-uuid")] Value::Uuid(Some(v)) => write!(s, "'{}'", v).unwrap(), #[cfg(feature = "postgres-array")] - Value::Array(Some(v)) => write!( + Value::Array(_, Some(v)) => write!( s, "'{{{}}}'", v.iter() diff --git a/src/query/insert.rs b/src/query/insert.rs index dd1a6bab7..069bbc63b 100644 --- a/src/query/insert.rs +++ b/src/query/insert.rs @@ -198,10 +198,7 @@ impl InsertStatement { where I: IntoIterator, { - let values = values - .into_iter() - .map(|v| v.into()) - .collect::>(); + let values = values.into_iter().collect::>(); if self.columns.len() != values.len() { return Err(Error::ColValNumMismatch { col_len: self.columns.len(), diff --git a/src/table/column.rs b/src/table/column.rs index db4bc8fd5..78b1940ec 100644 --- a/src/table/column.rs +++ b/src/table/column.rs @@ -45,7 +45,7 @@ pub enum ColumnType { name: DynIden, variants: Vec, }, - Array(Option), + Array(SeaRc>), Cidr, Inet, MacAddr, @@ -64,7 +64,7 @@ pub enum ColumnSpec { } // All interval fields -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Eq, PartialEq)] pub enum PgInterval { Year, Month, @@ -524,8 +524,8 @@ impl ColumnDef { /// Set column type as an array with a specified element type. /// This is only supported on Postgres. - pub fn array(&mut self, elem_type: String) -> &mut Self { - self.types = Some(ColumnType::Array(Some(elem_type))); + pub fn array(&mut self, elem_type: ColumnType) -> &mut Self { + self.types = Some(ColumnType::Array(SeaRc::new(Box::new(elem_type)))); self } diff --git a/src/types.rs b/src/types.rs index 00ebe7b83..3c147c303 100644 --- a/src/types.rs +++ b/src/types.rs @@ -23,9 +23,9 @@ macro_rules! iden_trait { } fn to_string(&self) -> String { - let s = &mut String::new(); - self.unquoted(s); - s.to_owned() + let mut s = String::new(); + self.unquoted(&mut s); + s } fn unquoted(&self, s: &mut dyn fmt::Write); diff --git a/src/value.rs b/src/value.rs index 5f3192a50..323e29254 100644 --- a/src/value.rs +++ b/src/value.rs @@ -31,6 +31,89 @@ use mac_address::MacAddress; use crate::{BlobSize, ColumnType, CommonSqlQueryBuilder, QueryBuilder}; +/// [`Value`] types variant for Postgres array +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum ArrayType { + Bool, + TinyInt, + SmallInt, + Int, + BigInt, + TinyUnsigned, + SmallUnsigned, + Unsigned, + BigUnsigned, + Float, + Double, + String, + Char, + Bytes, + + #[cfg(feature = "with-json")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))] + Json, + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + ChronoDate, + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + ChronoTime, + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + ChronoDateTime, + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + ChronoDateTimeUtc, + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + ChronoDateTimeLocal, + + #[cfg(feature = "with-chrono")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] + ChronoDateTimeWithTimeZone, + + #[cfg(feature = "with-time")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] + TimeDate, + + #[cfg(feature = "with-time")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] + TimeTime, + + #[cfg(feature = "with-time")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] + TimeDateTime, + + #[cfg(feature = "with-time")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] + TimeDateTimeWithTimeZone, + + #[cfg(feature = "with-uuid")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))] + Uuid, + + #[cfg(feature = "with-rust_decimal")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-rust_decimal")))] + Decimal, + + #[cfg(feature = "with-bigdecimal")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-bigdecimal")))] + BigDecimal, + + #[cfg(feature = "with-ipnetwork")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-ipnetwork")))] + IpNetwork, + + #[cfg(feature = "with-mac_address")] + #[cfg_attr(docsrs, doc(cfg(feature = "with-mac_address")))] + MacAddress, +} + /// Value variants /// /// We want Value to be exactly 1 pointer sized, so anything larger should be boxed. @@ -111,7 +194,7 @@ pub enum Value { #[cfg(feature = "postgres-array")] #[cfg_attr(docsrs, doc(cfg(feature = "postgres-array")))] - Array(Option>>), + Array(ArrayType, Option>>), #[cfg(feature = "with-ipnetwork")] #[cfg_attr(docsrs, doc(cfg(feature = "with-ipnetwork")))] @@ -135,8 +218,14 @@ pub trait ValueType: Sized { Self::try_from(v).unwrap() } + fn expect(v: Value, msg: &str) -> Self { + Self::try_from(v).expect(msg) + } + fn type_name() -> String; + fn array_type() -> ArrayType; + fn column_type() -> ColumnType; } @@ -185,6 +274,13 @@ impl Value { { T::unwrap(self) } + + pub fn expect(self, msg: &str) -> T + where + T: ValueType, + { + T::expect(self, msg) + } } macro_rules! type_to_value { @@ -213,6 +309,10 @@ macro_rules! type_to_value { stringify!($type).to_owned() } + fn array_type() -> ArrayType { + ArrayType::$name + } + fn column_type() -> ColumnType { use ColumnType::*; $col_type @@ -247,6 +347,10 @@ macro_rules! type_to_box_value { stringify!($type).to_owned() } + fn array_type() -> ArrayType { + ArrayType::$name + } + fn column_type() -> ColumnType { use ColumnType::*; $col_type @@ -315,6 +419,10 @@ where format!("Option<{}>", T::type_name()) } + fn array_type() -> ArrayType { + T::array_type() + } + fn column_type() -> ColumnType { T::column_type() } @@ -378,6 +486,10 @@ mod with_chrono { stringify!(DateTime).to_owned() } + fn array_type() -> ArrayType { + ArrayType::ChronoDateTimeUtc + } + fn column_type() -> ColumnType { ColumnType::TimestampWithTimeZone(None) } @@ -401,6 +513,10 @@ mod with_chrono { stringify!(DateTime).to_owned() } + fn array_type() -> ArrayType { + ArrayType::ChronoDateTimeLocal + } + fn column_type() -> ColumnType { ColumnType::TimestampWithTimeZone(None) } @@ -424,6 +540,10 @@ mod with_chrono { stringify!(DateTime).to_owned() } + fn array_type() -> ArrayType { + ArrayType::ChronoDateTimeWithTimeZone + } + fn column_type() -> ColumnType { ColumnType::TimestampWithTimeZone(None) } @@ -479,6 +599,10 @@ mod with_time { stringify!(OffsetDateTime).to_owned() } + fn array_type() -> ArrayType { + ArrayType::TimeDateTimeWithTimeZone + } + fn column_type() -> ColumnType { ColumnType::TimestampWithTimeZone(None) } @@ -529,6 +653,7 @@ mod with_mac_address { #[cfg_attr(docsrs, doc(cfg(feature = "postgres-array")))] mod with_array { use super::*; + use crate::SeaRc; // We only imlement conversion from Vec to Array when T is not u8. // This is because for u8's case, there is already conversion to Byte defined above. @@ -545,7 +670,11 @@ mod with_array { impl NotU8 for u64 {} impl NotU8 for f32 {} impl NotU8 for f64 {} + impl NotU8 for char {} impl NotU8 for String {} + impl NotU8 for Vec {} + + // TODO impl NotU8 for Option {} #[cfg(feature = "with-json")] impl NotU8 for Json {} @@ -583,21 +712,30 @@ mod with_array { #[cfg(feature = "with-uuid")] impl NotU8 for Uuid {} + #[cfg(feature = "with-ipnetwork")] + impl NotU8 for IpNetwork {} + + #[cfg(feature = "with-mac_address")] + impl NotU8 for MacAddress {} + impl From> for Value where - T: Into + NotU8, + T: Into + NotU8 + ValueType, { fn from(x: Vec) -> Value { - Value::Array(Some(Box::new(x.into_iter().map(|e| e.into()).collect()))) + Value::Array( + T::array_type(), + Some(Box::new(x.into_iter().map(|e| e.into()).collect())), + ) } } impl Nullable for Vec where - T: Into + NotU8, + T: Into + NotU8 + ValueType, { fn null() -> Value { - Value::Array(None) + Value::Array(T::array_type(), None) } } @@ -607,7 +745,9 @@ mod with_array { { fn try_from(v: Value) -> Result { match v { - Value::Array(Some(v)) => Ok(v.into_iter().map(|e| e.unwrap()).collect()), + Value::Array(ty, Some(v)) if T::array_type() == ty => { + Ok(v.into_iter().map(|e| e.unwrap()).collect()) + } _ => Err(ValueTypeErr), } } @@ -616,9 +756,13 @@ mod with_array { stringify!(Vec).to_owned() } + fn array_type() -> ArrayType { + T::array_type() + } + fn column_type() -> ColumnType { use ColumnType::*; - Array(None) + Array(SeaRc::new(Box::new(T::column_type()))) } } } @@ -880,12 +1024,12 @@ impl Value { #[cfg(feature = "postgres-array")] impl Value { pub fn is_array(&self) -> bool { - matches!(self, Self::Array(_)) + matches!(self, Self::Array(_, _)) } pub fn as_ref_array(&self) -> Option<&Vec> { match self { - Self::Array(v) => box_to_opt_ref!(v), + Self::Array(_, v) => box_to_opt_ref!(v), _ => panic!("not Value::Array"), } } @@ -1172,7 +1316,7 @@ pub fn sea_value_to_json_value(value: &Value) -> Json { #[cfg(feature = "with-uuid")] Value::Uuid(None) => Json::Null, #[cfg(feature = "postgres-array")] - Value::Array(None) => Json::Null, + Value::Array(_, None) => Json::Null, #[cfg(feature = "with-ipnetwork")] Value::IpNetwork(None) => Json::Null, #[cfg(feature = "with-mac_address")] @@ -1225,7 +1369,7 @@ pub fn sea_value_to_json_value(value: &Value) -> Json { #[cfg(feature = "with-uuid")] Value::Uuid(Some(v)) => Json::String(v.to_string()), #[cfg(feature = "postgres-array")] - Value::Array(Some(v)) => { + Value::Array(_, Some(v)) => { Json::Array(v.as_ref().iter().map(sea_value_to_json_value).collect()) } #[cfg(feature = "with-ipnetwork")] @@ -1616,7 +1760,7 @@ mod tests { #[test] #[cfg(feature = "with-uuid")] fn test_uuid_value() { - let uuid = uuid::Uuid::parse_str("936DA01F9ABD4d9d80C702AF85C822A8").unwrap(); + let uuid = Uuid::parse_str("936DA01F9ABD4d9d80C702AF85C822A8").unwrap(); let value: Value = uuid.into(); let out: uuid::Uuid = value.unwrap(); assert_eq!(out, uuid); @@ -1642,4 +1786,12 @@ mod tests { let out: Vec = v.unwrap(); assert_eq!(out, vec![1, 2, 3, 4, 5]); } + + #[test] + #[cfg(feature = "postgres-array")] + fn test_option_array_value() { + let v: Value = Value::Array(ArrayType::Int, None); + let out: Option> = v.unwrap(); + assert_eq!(out, None); + } }