From a403531c7b9cc5be7a3ffae89826cdb1ca7653e0 Mon Sep 17 00:00:00 2001 From: Ramzi Sabra Date: Mon, 2 Oct 2023 07:05:18 +0300 Subject: [PATCH 1/4] added PostgreSQL Interval value variant --- Cargo.toml | 3 +- sea-query-binder/Cargo.toml | 3 +- sea-query-binder/src/sqlx_mysql.rs | 2 + sea-query-binder/src/sqlx_postgres.rs | 12 ++++++ sea-query-binder/src/sqlx_sqlite.rs | 2 + src/backend/query_builder.rs | 30 ++++++++++++++ src/value.rs | 56 +++++++++++++++++++++++++++ 7 files changed, 106 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 37ed7ebc9..ef4b72853 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ time = { version = "0.3", default-features = false, optional = true, features = ipnetwork = { version = "0.20", default-features = false, optional = true } mac_address = { version = "1.1", default-features = false, optional = true } ordered-float = { version = "3.4", default-features = false, optional = true } +sqlx = { git = "https://github.com/yasamoka/sqlx", branch = "pg-interval-hash", default-features = false, optional = true } [dev-dependencies] sea-query = { path = ".", features = ["tests-cfg"] } @@ -60,7 +61,7 @@ derive = ["sea-query-derive"] attr = ["sea-query-attr"] hashable-value = ["derivative", "ordered-float"] postgres-array = [] -postgres-interval = ["proc-macro2", "quote"] +postgres-interval = ["proc-macro2", "quote", "sqlx/postgres"] thread-safe = [] with-chrono = ["chrono"] with-json = ["serde_json"] diff --git a/sea-query-binder/Cargo.toml b/sea-query-binder/Cargo.toml index ca121a2a2..5a3097bbe 100644 --- a/sea-query-binder/Cargo.toml +++ b/sea-query-binder/Cargo.toml @@ -18,7 +18,7 @@ rust-version = "1.60" [dependencies] sea-query = { version = "0.31", path = "..", default-features = false, features = ["thread-safe"] } -sqlx = { version = "0.7", default-features = false, optional = true } +sqlx = { git = "https://github.com/yasamoka/sqlx", branch = "pg-interval-hash", default-features = false, optional = true } serde_json = { version = "1", default-features = false, optional = true, features = ["std"] } chrono = { version = "0.4", default-features = false, optional = true, features = ["clock"] } rust_decimal = { version = "1", default-features = false, optional = true } @@ -42,6 +42,7 @@ 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"] +postgres-interval = ["sqlx-postgres", "sqlx/chrono"] runtime-async-std-native-tls = ["sqlx?/runtime-async-std-native-tls"] runtime-async-std-rustls = ["sqlx?/runtime-async-std-rustls", ] runtime-actix-native-tls = ["sqlx?/runtime-tokio-native-tls"] diff --git a/sea-query-binder/src/sqlx_mysql.rs b/sea-query-binder/src/sqlx_mysql.rs index abeac3862..301ba7abd 100644 --- a/sea-query-binder/src/sqlx_mysql.rs +++ b/sea-query-binder/src/sqlx_mysql.rs @@ -73,6 +73,8 @@ impl sqlx::IntoArguments<'_, sqlx::mysql::MySql> for SqlxValues { Value::ChronoDateTimeWithTimeZone(t) => { args.add(Value::ChronoDateTimeWithTimeZone(t).chrono_as_naive_utc_in_string()); } + #[cfg(all(feature = "with-chrono", feature = "postgres-interval"))] + Value::Interval(_) => {} #[cfg(feature = "with-time")] Value::TimeDate(t) => { args.add(t.as_deref()); diff --git a/sea-query-binder/src/sqlx_postgres.rs b/sea-query-binder/src/sqlx_postgres.rs index b0fce77a0..2af413f23 100644 --- a/sea-query-binder/src/sqlx_postgres.rs +++ b/sea-query-binder/src/sqlx_postgres.rs @@ -10,6 +10,8 @@ use mac_address::MacAddress; use rust_decimal::Decimal; #[cfg(feature = "with-json")] use serde_json::Value as Json; +#[cfg(feature = "postgres-interval")] +use sqlx::postgres::types::PgInterval; #[cfg(feature = "with-uuid")] use uuid::Uuid; @@ -89,6 +91,10 @@ impl sqlx::IntoArguments<'_, sqlx::postgres::Postgres> for SqlxValues { Value::ChronoDateTimeWithTimeZone(t) => { args.add(t.as_deref()); } + #[cfg(feature = "postgres-interval")] + Value::Interval(t) => { + args.add(t.as_deref()); + } #[cfg(feature = "with-time")] Value::TimeDate(t) => { args.add(t.as_deref()); @@ -252,6 +258,12 @@ impl sqlx::IntoArguments<'_, sqlx::postgres::Postgres> for SqlxValues { ); args.add(value); } + #[cfg(feature = "postgres-interval")] + ArrayType::Interval => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Interval"); + args.add(value); + } #[cfg(feature = "with-time")] ArrayType::TimeDate => { let value: Option> = Value::Array(ty, v) diff --git a/sea-query-binder/src/sqlx_sqlite.rs b/sea-query-binder/src/sqlx_sqlite.rs index d100ef6d2..c7fc0e2fa 100644 --- a/sea-query-binder/src/sqlx_sqlite.rs +++ b/sea-query-binder/src/sqlx_sqlite.rs @@ -73,6 +73,8 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::sqlite::Sqlite> for SqlxValues { Value::ChronoDateTimeWithTimeZone(t) => { args.add(t.map(|t| *t)); } + #[cfg(all(feature = "with-chrono", feature = "postgres-interval"))] + Value::Interval(_) => {} #[cfg(feature = "with-time")] Value::TimeDate(t) => { args.add(t.map(|t| *t)); diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index fd8c7677d..134e7adf1 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -1002,6 +1002,8 @@ pub trait QueryBuilder: Value::ChronoDateTimeLocal(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-chrono")] Value::ChronoDateTimeWithTimeZone(None) => write!(s, "NULL").unwrap(), + #[cfg(feature = "postgres-interval")] + Value::Interval(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-time")] Value::TimeDate(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-time")] @@ -1060,6 +1062,34 @@ pub trait QueryBuilder: Value::ChronoDateTimeWithTimeZone(Some(v)) => { write!(s, "'{}'", v.format("%Y-%m-%d %H:%M:%S %:z")).unwrap() } + #[cfg(feature = "postgres-interval")] + Value::Interval(Some(v)) => { + let mut space = false; + + write!(s, "'").unwrap(); + + if v.months > 0 { + write!(s, "{} MONTHS", v.days).unwrap(); + space = true; + } + + if v.days > 0 { + if space { + write!(s, " ").unwrap(); + } + write!(s, "{} DAYS", v.days).unwrap(); + space = true; + } + + if v.microseconds > 0 { + if space { + write!(s, " ").unwrap(); + } + write!(s, "{} MICROSECONDS", v.microseconds).unwrap(); + } + + write!(s, "'::interval").unwrap(); + } #[cfg(feature = "with-time")] Value::TimeDate(Some(v)) => { write!(s, "'{}'", v.format(time_format::FORMAT_DATE).unwrap()).unwrap() diff --git a/src/value.rs b/src/value.rs index 7049d744a..6ca4b0681 100644 --- a/src/value.rs +++ b/src/value.rs @@ -32,6 +32,8 @@ use std::net::IpAddr; use mac_address::MacAddress; use crate::{BlobSize, ColumnType, CommonSqlQueryBuilder, QueryBuilder}; +#[cfg(feature = "postgres-interval")] +use sqlx::postgres::types::PgInterval; /// [`Value`] types variant for Postgres array #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -79,6 +81,9 @@ pub enum ArrayType { #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] ChronoDateTimeWithTimeZone, + #[cfg(all(feature = "with-chrono", feature = "postgres-interval"))] + Interval, + #[cfg(feature = "with-time")] #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] TimeDate, @@ -202,6 +207,9 @@ pub enum Value { #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] ChronoDateTimeWithTimeZone(Option>>), + #[cfg(feature = "postgres-interval")] + Interval(Option>), + #[cfg(feature = "with-time")] #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] TimeDate(Option>), @@ -540,6 +548,13 @@ mod with_chrono { } } + #[cfg(feature = "postgres-interval")] + impl From for Value { + fn from(v: PgInterval) -> Self { + Value::Interval(Some(Box::new(v))) + } + } + impl Nullable for DateTime { fn null() -> Value { Value::ChronoDateTimeUtc(None) @@ -594,6 +609,28 @@ mod with_chrono { } } + #[cfg(feature = "postgres-interval")] + impl ValueType for PgInterval { + fn try_from(v: Value) -> Result { + match v { + Value::Interval(Some(x)) => Ok(*x), + _ => Err(ValueTypeErr), + } + } + + fn type_name() -> String { + stringify!(PgInterval).to_owned() + } + + fn array_type() -> ArrayType { + ArrayType::Interval + } + + fn column_type() -> ColumnType { + ColumnType::Interval(None, None) + } + } + impl Nullable for DateTime { fn null() -> Value { Value::ChronoDateTimeWithTimeZone(None) @@ -805,6 +842,9 @@ pub mod with_array { #[cfg(feature = "with-chrono")] impl NotU8 for DateTime where Tz: chrono::TimeZone {} + #[cfg(feature = "postgres-interval")] + impl NotU8 for PgInterval {} + #[cfg(feature = "with-time")] impl NotU8 for time::Date {} @@ -1161,6 +1201,20 @@ impl Value { } } +#[cfg(feature = "postgres-interval")] +impl Value { + pub fn is_interval(&self) -> bool { + matches!(self, Self::Interval(_)) + } + + pub fn as_ref_interval(&self) -> Option<&PgInterval> { + match self { + Self::Interval(v) => box_to_opt_ref!(v), + _ => panic!("not Value::Interval"), + } + } +} + #[cfg(feature = "with-ipnetwork")] impl Value { pub fn is_ipnetwork(&self) -> bool { @@ -1423,6 +1477,8 @@ pub fn sea_value_to_json_value(value: &Value) -> Json { Value::ChronoDateTimeUtc(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-chrono")] Value::ChronoDateTimeLocal(_) => CommonSqlQueryBuilder.value_to_string(value).into(), + #[cfg(all(feature = "with-chrono", feature = "postgres-interval"))] + Value::Interval(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-time")] Value::TimeDate(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-time")] From 1c594ea561740df8663c76d8c8883ec3c49d9998 Mon Sep 17 00:00:00 2001 From: Ramzi Sabra Date: Mon, 2 Oct 2023 07:43:14 +0300 Subject: [PATCH 2/4] replaced redundant cfg attribute --- sea-query-binder/src/sqlx_mysql.rs | 2 +- sea-query-binder/src/sqlx_sqlite.rs | 2 +- src/value.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sea-query-binder/src/sqlx_mysql.rs b/sea-query-binder/src/sqlx_mysql.rs index 301ba7abd..9400c3fd2 100644 --- a/sea-query-binder/src/sqlx_mysql.rs +++ b/sea-query-binder/src/sqlx_mysql.rs @@ -73,7 +73,7 @@ impl sqlx::IntoArguments<'_, sqlx::mysql::MySql> for SqlxValues { Value::ChronoDateTimeWithTimeZone(t) => { args.add(Value::ChronoDateTimeWithTimeZone(t).chrono_as_naive_utc_in_string()); } - #[cfg(all(feature = "with-chrono", feature = "postgres-interval"))] + #[cfg(feature = "postgres-interval")] Value::Interval(_) => {} #[cfg(feature = "with-time")] Value::TimeDate(t) => { diff --git a/sea-query-binder/src/sqlx_sqlite.rs b/sea-query-binder/src/sqlx_sqlite.rs index c7fc0e2fa..4c6cd5a4f 100644 --- a/sea-query-binder/src/sqlx_sqlite.rs +++ b/sea-query-binder/src/sqlx_sqlite.rs @@ -73,7 +73,7 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::sqlite::Sqlite> for SqlxValues { Value::ChronoDateTimeWithTimeZone(t) => { args.add(t.map(|t| *t)); } - #[cfg(all(feature = "with-chrono", feature = "postgres-interval"))] + #[cfg(feature = "postgres-interval")] Value::Interval(_) => {} #[cfg(feature = "with-time")] Value::TimeDate(t) => { diff --git a/src/value.rs b/src/value.rs index 6ca4b0681..2a6246d9e 100644 --- a/src/value.rs +++ b/src/value.rs @@ -81,7 +81,7 @@ pub enum ArrayType { #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] ChronoDateTimeWithTimeZone, - #[cfg(all(feature = "with-chrono", feature = "postgres-interval"))] + #[cfg(feature = "postgres-interval")] Interval, #[cfg(feature = "with-time")] @@ -1477,7 +1477,7 @@ pub fn sea_value_to_json_value(value: &Value) -> Json { Value::ChronoDateTimeUtc(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-chrono")] Value::ChronoDateTimeLocal(_) => CommonSqlQueryBuilder.value_to_string(value).into(), - #[cfg(all(feature = "with-chrono", feature = "postgres-interval"))] + #[cfg(feature = "postgres-interval")] Value::Interval(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-time")] Value::TimeDate(_) => CommonSqlQueryBuilder.value_to_string(value).into(), From e9898ec72e1063b6d4353f080ece3db6f69af229 Mon Sep 17 00:00:00 2001 From: Ramzi Sabra Date: Tue, 10 Oct 2023 13:43:07 +0300 Subject: [PATCH 3/4] moved PgInterval internally; reorganized PgInterval-related additions --- Cargo.toml | 3 +- sea-query-binder/Cargo.toml | 2 +- sea-query-binder/src/sqlx_mysql.rs | 4 +- sea-query-binder/src/sqlx_postgres.rs | 24 ++--- sea-query-binder/src/sqlx_sqlite.rs | 4 +- src/backend/query_builder.rs | 42 ++++----- src/value.rs | 121 ++++++++++++++------------ 7 files changed, 104 insertions(+), 96 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ef4b72853..37ed7ebc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,6 @@ time = { version = "0.3", default-features = false, optional = true, features = ipnetwork = { version = "0.20", default-features = false, optional = true } mac_address = { version = "1.1", default-features = false, optional = true } ordered-float = { version = "3.4", default-features = false, optional = true } -sqlx = { git = "https://github.com/yasamoka/sqlx", branch = "pg-interval-hash", default-features = false, optional = true } [dev-dependencies] sea-query = { path = ".", features = ["tests-cfg"] } @@ -61,7 +60,7 @@ derive = ["sea-query-derive"] attr = ["sea-query-attr"] hashable-value = ["derivative", "ordered-float"] postgres-array = [] -postgres-interval = ["proc-macro2", "quote", "sqlx/postgres"] +postgres-interval = ["proc-macro2", "quote"] thread-safe = [] with-chrono = ["chrono"] with-json = ["serde_json"] diff --git a/sea-query-binder/Cargo.toml b/sea-query-binder/Cargo.toml index 5a3097bbe..691065cb8 100644 --- a/sea-query-binder/Cargo.toml +++ b/sea-query-binder/Cargo.toml @@ -18,7 +18,7 @@ rust-version = "1.60" [dependencies] sea-query = { version = "0.31", path = "..", default-features = false, features = ["thread-safe"] } -sqlx = { git = "https://github.com/yasamoka/sqlx", branch = "pg-interval-hash", default-features = false, optional = true } +sqlx = { version = "0.7", default-features = false, optional = true } serde_json = { version = "1", default-features = false, optional = true, features = ["std"] } chrono = { version = "0.4", default-features = false, optional = true, features = ["clock"] } rust_decimal = { version = "1", default-features = false, optional = true } diff --git a/sea-query-binder/src/sqlx_mysql.rs b/sea-query-binder/src/sqlx_mysql.rs index 9400c3fd2..cb15c2cd9 100644 --- a/sea-query-binder/src/sqlx_mysql.rs +++ b/sea-query-binder/src/sqlx_mysql.rs @@ -73,8 +73,6 @@ impl sqlx::IntoArguments<'_, sqlx::mysql::MySql> for SqlxValues { Value::ChronoDateTimeWithTimeZone(t) => { args.add(Value::ChronoDateTimeWithTimeZone(t).chrono_as_naive_utc_in_string()); } - #[cfg(feature = "postgres-interval")] - Value::Interval(_) => {} #[cfg(feature = "with-time")] Value::TimeDate(t) => { args.add(t.as_deref()); @@ -91,6 +89,8 @@ impl sqlx::IntoArguments<'_, sqlx::mysql::MySql> for SqlxValues { Value::TimeDateTimeWithTimeZone(t) => { args.add(t.as_deref()); } + #[cfg(feature = "postgres-interval")] + Value::Interval(_) => {} #[cfg(feature = "with-uuid")] Value::Uuid(uuid) => { args.add(uuid.as_deref()); diff --git a/sea-query-binder/src/sqlx_postgres.rs b/sea-query-binder/src/sqlx_postgres.rs index 2af413f23..6bbd795d3 100644 --- a/sea-query-binder/src/sqlx_postgres.rs +++ b/sea-query-binder/src/sqlx_postgres.rs @@ -8,10 +8,10 @@ use ipnetwork::IpNetwork; use mac_address::MacAddress; #[cfg(feature = "with-rust_decimal")] use rust_decimal::Decimal; +#[cfg(feature = "postgres-interval")] +use sea_query::types::PgInterval; #[cfg(feature = "with-json")] use serde_json::Value as Json; -#[cfg(feature = "postgres-interval")] -use sqlx::postgres::types::PgInterval; #[cfg(feature = "with-uuid")] use uuid::Uuid; @@ -91,10 +91,6 @@ impl sqlx::IntoArguments<'_, sqlx::postgres::Postgres> for SqlxValues { Value::ChronoDateTimeWithTimeZone(t) => { args.add(t.as_deref()); } - #[cfg(feature = "postgres-interval")] - Value::Interval(t) => { - args.add(t.as_deref()); - } #[cfg(feature = "with-time")] Value::TimeDate(t) => { args.add(t.as_deref()); @@ -111,6 +107,10 @@ impl sqlx::IntoArguments<'_, sqlx::postgres::Postgres> for SqlxValues { Value::TimeDateTimeWithTimeZone(t) => { args.add(t.as_deref()); } + #[cfg(feature = "postgres-interval")] + Value::Interval(t) => { + args.add(t.as_deref()); + } #[cfg(feature = "with-uuid")] Value::Uuid(uuid) => { args.add(uuid.as_deref()); @@ -258,12 +258,6 @@ impl sqlx::IntoArguments<'_, sqlx::postgres::Postgres> for SqlxValues { ); args.add(value); } - #[cfg(feature = "postgres-interval")] - ArrayType::Interval => { - let value: Option> = Value::Array(ty, v) - .expect("This Value::Array should consist of Value::Interval"); - args.add(value); - } #[cfg(feature = "with-time")] ArrayType::TimeDate => { let value: Option> = Value::Array(ty, v) @@ -289,6 +283,12 @@ impl sqlx::IntoArguments<'_, sqlx::postgres::Postgres> for SqlxValues { ); args.add(value); } + #[cfg(feature = "postgres-interval")] + ArrayType::Interval => { + let value: Option> = Value::Array(ty, v) + .expect("This Value::Array should consist of Value::Interval"); + args.add(value); + } #[cfg(feature = "with-uuid")] ArrayType::Uuid => { let value: Option> = Value::Array(ty, v) diff --git a/sea-query-binder/src/sqlx_sqlite.rs b/sea-query-binder/src/sqlx_sqlite.rs index 4c6cd5a4f..2425603bf 100644 --- a/sea-query-binder/src/sqlx_sqlite.rs +++ b/sea-query-binder/src/sqlx_sqlite.rs @@ -73,8 +73,6 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::sqlite::Sqlite> for SqlxValues { Value::ChronoDateTimeWithTimeZone(t) => { args.add(t.map(|t| *t)); } - #[cfg(feature = "postgres-interval")] - Value::Interval(_) => {} #[cfg(feature = "with-time")] Value::TimeDate(t) => { args.add(t.map(|t| *t)); @@ -91,6 +89,8 @@ impl<'q> sqlx::IntoArguments<'q, sqlx::sqlite::Sqlite> for SqlxValues { Value::TimeDateTimeWithTimeZone(t) => { args.add(t.map(|t| *t)); } + #[cfg(feature = "postgres-interval")] + Value::Interval(_) => {} #[cfg(feature = "with-uuid")] Value::Uuid(uuid) => { args.add(uuid.map(|uuid| *uuid)); diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index 134e7adf1..520e3c9d0 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -1002,8 +1002,6 @@ pub trait QueryBuilder: Value::ChronoDateTimeLocal(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-chrono")] Value::ChronoDateTimeWithTimeZone(None) => write!(s, "NULL").unwrap(), - #[cfg(feature = "postgres-interval")] - Value::Interval(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-time")] Value::TimeDate(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-time")] @@ -1012,6 +1010,8 @@ pub trait QueryBuilder: Value::TimeDateTime(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-time")] Value::TimeDateTimeWithTimeZone(None) => write!(s, "NULL").unwrap(), + #[cfg(feature = "postgres-interval")] + Value::Interval(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-rust_decimal")] Value::Decimal(None) => write!(s, "NULL").unwrap(), #[cfg(feature = "with-bigdecimal")] @@ -1062,6 +1062,25 @@ pub trait QueryBuilder: Value::ChronoDateTimeWithTimeZone(Some(v)) => { write!(s, "'{}'", v.format("%Y-%m-%d %H:%M:%S %:z")).unwrap() } + #[cfg(feature = "with-time")] + Value::TimeDate(Some(v)) => { + write!(s, "'{}'", v.format(time_format::FORMAT_DATE).unwrap()).unwrap() + } + #[cfg(feature = "with-time")] + Value::TimeTime(Some(v)) => { + write!(s, "'{}'", v.format(time_format::FORMAT_TIME).unwrap()).unwrap() + } + #[cfg(feature = "with-time")] + Value::TimeDateTime(Some(v)) => { + write!(s, "'{}'", v.format(time_format::FORMAT_DATETIME).unwrap()).unwrap() + } + #[cfg(feature = "with-time")] + Value::TimeDateTimeWithTimeZone(Some(v)) => write!( + s, + "'{}'", + v.format(time_format::FORMAT_DATETIME_TZ).unwrap() + ) + .unwrap(), #[cfg(feature = "postgres-interval")] Value::Interval(Some(v)) => { let mut space = false; @@ -1090,25 +1109,6 @@ pub trait QueryBuilder: write!(s, "'::interval").unwrap(); } - #[cfg(feature = "with-time")] - Value::TimeDate(Some(v)) => { - write!(s, "'{}'", v.format(time_format::FORMAT_DATE).unwrap()).unwrap() - } - #[cfg(feature = "with-time")] - Value::TimeTime(Some(v)) => { - write!(s, "'{}'", v.format(time_format::FORMAT_TIME).unwrap()).unwrap() - } - #[cfg(feature = "with-time")] - Value::TimeDateTime(Some(v)) => { - write!(s, "'{}'", v.format(time_format::FORMAT_DATETIME).unwrap()).unwrap() - } - #[cfg(feature = "with-time")] - Value::TimeDateTimeWithTimeZone(Some(v)) => write!( - s, - "'{}'", - v.format(time_format::FORMAT_DATETIME_TZ).unwrap() - ) - .unwrap(), #[cfg(feature = "with-rust_decimal")] Value::Decimal(Some(v)) => write!(s, "{v}").unwrap(), #[cfg(feature = "with-bigdecimal")] diff --git a/src/value.rs b/src/value.rs index 2a6246d9e..d01fa9ebc 100644 --- a/src/value.rs +++ b/src/value.rs @@ -32,8 +32,6 @@ use std::net::IpAddr; use mac_address::MacAddress; use crate::{BlobSize, ColumnType, CommonSqlQueryBuilder, QueryBuilder}; -#[cfg(feature = "postgres-interval")] -use sqlx::postgres::types::PgInterval; /// [`Value`] types variant for Postgres array #[derive(Clone, Debug, Eq, PartialEq, Hash)] @@ -81,9 +79,6 @@ pub enum ArrayType { #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] ChronoDateTimeWithTimeZone, - #[cfg(feature = "postgres-interval")] - Interval, - #[cfg(feature = "with-time")] #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] TimeDate, @@ -100,6 +95,9 @@ pub enum ArrayType { #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] TimeDateTimeWithTimeZone, + #[cfg(feature = "postgres-interval")] + Interval, + #[cfg(feature = "with-uuid")] #[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))] Uuid, @@ -207,9 +205,6 @@ pub enum Value { #[cfg_attr(docsrs, doc(cfg(feature = "with-chrono")))] ChronoDateTimeWithTimeZone(Option>>), - #[cfg(feature = "postgres-interval")] - Interval(Option>), - #[cfg(feature = "with-time")] #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] TimeDate(Option>), @@ -226,6 +221,9 @@ pub enum Value { #[cfg_attr(docsrs, doc(cfg(feature = "with-time")))] TimeDateTimeWithTimeZone(Option>), + #[cfg(feature = "postgres-interval")] + Interval(Option>), + #[cfg(feature = "with-uuid")] #[cfg_attr(docsrs, doc(cfg(feature = "with-uuid")))] Uuid(Option>), @@ -510,6 +508,14 @@ impl ValueType for Cow<'_, str> { type_to_box_value!(Vec, Bytes, Binary(BlobSize::Blob(None))); type_to_box_value!(String, String, String(None)); +#[cfg(feature = "postgres-interval")] +#[derive(Debug, Eq, PartialEq, Clone, Hash)] + pub struct PgIntervalValue { + pub months: i32, + pub days: i32, + pub microseconds: i64, + } + #[cfg(feature = "with-json")] #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))] mod with_json { @@ -548,13 +554,6 @@ mod with_chrono { } } - #[cfg(feature = "postgres-interval")] - impl From for Value { - fn from(v: PgInterval) -> Self { - Value::Interval(Some(Box::new(v))) - } - } - impl Nullable for DateTime { fn null() -> Value { Value::ChronoDateTimeUtc(None) @@ -609,28 +608,6 @@ mod with_chrono { } } - #[cfg(feature = "postgres-interval")] - impl ValueType for PgInterval { - fn try_from(v: Value) -> Result { - match v { - Value::Interval(Some(x)) => Ok(*x), - _ => Err(ValueTypeErr), - } - } - - fn type_name() -> String { - stringify!(PgInterval).to_owned() - } - - fn array_type() -> ArrayType { - ArrayType::Interval - } - - fn column_type() -> ColumnType { - ColumnType::Interval(None, None) - } - } - impl Nullable for DateTime { fn null() -> Value { Value::ChronoDateTimeWithTimeZone(None) @@ -718,6 +695,38 @@ mod with_time { } } +#[cfg(feature = "postgres-interval")] +mod with_postgres_interval { + use super::*; + + impl From for Value { + fn from(v: PgIntervalValue) -> Self { + Value::Interval(Some(Box::new(v))) + } + } + + impl ValueType for PgIntervalValue { + fn try_from(v: Value) -> Result { + match v { + Value::Interval(Some(x)) => Ok(*x), + _ => Err(ValueTypeErr), + } + } + + fn type_name() -> String { + stringify!(PgIntervalValue).to_owned() + } + + fn array_type() -> ArrayType { + ArrayType::Interval + } + + fn column_type() -> ColumnType { + ColumnType::Interval(None, None) + } + } +} + #[cfg(feature = "with-rust_decimal")] #[cfg_attr(docsrs, doc(cfg(feature = "with-rust_decimal")))] mod with_rust_decimal { @@ -842,9 +851,6 @@ pub mod with_array { #[cfg(feature = "with-chrono")] impl NotU8 for DateTime where Tz: chrono::TimeZone {} - #[cfg(feature = "postgres-interval")] - impl NotU8 for PgInterval {} - #[cfg(feature = "with-time")] impl NotU8 for time::Date {} @@ -857,6 +863,9 @@ pub mod with_array { #[cfg(feature = "with-time")] impl NotU8 for OffsetDateTime {} + #[cfg(feature = "postgres-interval")] + impl NotU8 for PgIntervalValue {} + #[cfg(feature = "with-rust_decimal")] impl NotU8 for Decimal {} @@ -1135,6 +1144,20 @@ impl Value { } } +#[cfg(feature = "postgres-interval")] +impl Value { + pub fn is_interval(&self) -> bool { + matches!(self, Self::Interval(_)) + } + + pub fn as_ref_interval(&self) -> Option<&PgIntervalValue> { + match self { + Self::Interval(v) => box_to_opt_ref!(v), + _ => panic!("not Value::Interval"), + } + } +} + #[cfg(feature = "with-rust_decimal")] impl Value { pub fn is_decimal(&self) -> bool { @@ -1201,20 +1224,6 @@ impl Value { } } -#[cfg(feature = "postgres-interval")] -impl Value { - pub fn is_interval(&self) -> bool { - matches!(self, Self::Interval(_)) - } - - pub fn as_ref_interval(&self) -> Option<&PgInterval> { - match self { - Self::Interval(v) => box_to_opt_ref!(v), - _ => panic!("not Value::Interval"), - } - } -} - #[cfg(feature = "with-ipnetwork")] impl Value { pub fn is_ipnetwork(&self) -> bool { @@ -1477,8 +1486,6 @@ pub fn sea_value_to_json_value(value: &Value) -> Json { Value::ChronoDateTimeUtc(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-chrono")] Value::ChronoDateTimeLocal(_) => CommonSqlQueryBuilder.value_to_string(value).into(), - #[cfg(feature = "postgres-interval")] - Value::Interval(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-time")] Value::TimeDate(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-time")] @@ -1487,6 +1494,8 @@ pub fn sea_value_to_json_value(value: &Value) -> Json { Value::TimeDateTime(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-time")] Value::TimeDateTimeWithTimeZone(_) => CommonSqlQueryBuilder.value_to_string(value).into(), + #[cfg(feature = "postgres-interval")] + Value::Interval(_) => CommonSqlQueryBuilder.value_to_string(value).into(), #[cfg(feature = "with-rust_decimal")] Value::Decimal(Some(v)) => { use rust_decimal::prelude::ToPrimitive; From c9c53dc447b9c2e57622fd0c6423ea8ff034d7d6 Mon Sep 17 00:00:00 2001 From: Ramzi Sabra Date: Tue, 10 Oct 2023 14:10:44 +0300 Subject: [PATCH 4/4] added tests for PgInterval --- src/backend/query_builder.rs | 2 +- src/value.rs | 122 +++++++++++++++++++++++++++++++++-- 2 files changed, 117 insertions(+), 7 deletions(-) diff --git a/src/backend/query_builder.rs b/src/backend/query_builder.rs index 520e3c9d0..705654777 100644 --- a/src/backend/query_builder.rs +++ b/src/backend/query_builder.rs @@ -1088,7 +1088,7 @@ pub trait QueryBuilder: write!(s, "'").unwrap(); if v.months > 0 { - write!(s, "{} MONTHS", v.days).unwrap(); + write!(s, "{} MONTHS", v.months).unwrap(); space = true; } diff --git a/src/value.rs b/src/value.rs index d01fa9ebc..0e1b28971 100644 --- a/src/value.rs +++ b/src/value.rs @@ -509,12 +509,12 @@ type_to_box_value!(Vec, Bytes, Binary(BlobSize::Blob(None))); type_to_box_value!(String, String, String(None)); #[cfg(feature = "postgres-interval")] -#[derive(Debug, Eq, PartialEq, Clone, Hash)] - pub struct PgIntervalValue { - pub months: i32, - pub days: i32, - pub microseconds: i64, - } +#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)] +pub struct PgIntervalValue { + pub months: i32, + pub days: i32, + pub microseconds: i64, +} #[cfg(feature = "with-json")] #[cfg_attr(docsrs, doc(cfg(feature = "with-json")))] @@ -1920,6 +1920,116 @@ mod tests { ); } + #[test] + #[cfg(feature = "postgres-interval")] + fn test_pginterval_value() { + let interval = PgIntervalValue { + months: 1, + days: 2, + microseconds: 300, + }; + let value: Value = interval.into(); + let out: PgIntervalValue = value.unwrap(); + assert_eq!(out, interval); + } + + #[test] + #[cfg(feature = "postgres-interval")] + fn test_pginterval_query() { + use crate::*; + + const VALUES: [(PgIntervalValue, &str); 10] = [ + ( + PgIntervalValue { + months: 0, + days: 0, + microseconds: 1, + }, + "1 MICROSECONDS", + ), + ( + PgIntervalValue { + months: 0, + days: 0, + microseconds: 100, + }, + "100 MICROSECONDS", + ), + ( + PgIntervalValue { + months: 0, + days: 1, + microseconds: 0, + }, + "1 DAYS", + ), + ( + PgIntervalValue { + months: 0, + days: 2, + microseconds: 0, + }, + "2 DAYS", + ), + ( + PgIntervalValue { + months: 0, + days: 2, + microseconds: 100, + }, + "2 DAYS 100 MICROSECONDS", + ), + ( + PgIntervalValue { + months: 1, + days: 0, + microseconds: 0, + }, + "1 MONTHS", + ), + ( + PgIntervalValue { + months: 2, + days: 0, + microseconds: 0, + }, + "2 MONTHS", + ), + ( + PgIntervalValue { + months: 2, + days: 0, + microseconds: 100, + }, + "2 MONTHS 100 MICROSECONDS", + ), + ( + PgIntervalValue { + months: 2, + days: 2, + microseconds: 0, + }, + "2 MONTHS 2 DAYS", + ), + ( + PgIntervalValue { + months: 2, + days: 2, + microseconds: 100, + }, + "2 MONTHS 2 DAYS 100 MICROSECONDS", + ), + ]; + + for (interval, formatted) in VALUES { + let query = Query::select().expr(interval).to_owned(); + assert_eq!( + query.to_string(PostgresQueryBuilder), + format!("SELECT '{formatted}'::interval") + ); + } + } + #[test] #[cfg(feature = "with-uuid")] fn test_uuid_value() {