From bcb3959379742cebe7e8374914b85bcc0c6e5df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20B=C3=B8ving?= Date: Sun, 29 Mar 2020 01:28:47 +0100 Subject: [PATCH] Add array of uuid, chrono, time, bigdecimal, and ipnetwork as well as JsonValue to query macro (#154) * Add array of uuid, chrono, time, bigdecimal, and ipnetwork to query macro * Comment out tests for arrays of BigDecimal Currently arrays of BigDecimal doesn't in query macros compile. As all of the other types work just fine, BigDecimal is simply omitted. * Add serde_json::Value to query macros This also adds serde_json as an optional dependency to sqlx_macros along side a new json feature flag. --- Cargo.lock | 1 + Cargo.toml | 2 +- sqlx-core/src/postgres/types/bigdecimal.rs | 14 +++ sqlx-core/src/postgres/types/ipnetwork.rs | 6 + sqlx-macros/Cargo.toml | 2 + sqlx-macros/src/database/postgres.rs | 39 +++++++ tests/postgres-types.rs | 121 +++++++++++++++++++++ 7 files changed, 184 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index bc8e6178b..dfb46db31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1817,6 +1817,7 @@ dependencies = [ "lazy_static", "proc-macro2", "quote", + "serde_json", "sqlx-core 0.3.0-alpha.2", "syn", "tokio 0.2.13", diff --git a/Cargo.toml b/Cargo.toml index 332378b97..b7a399274 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,7 +55,7 @@ bigdecimal = ["sqlx-core/bigdecimal", "sqlx-macros/bigdecimal"] chrono = [ "sqlx-core/chrono", "sqlx-macros/chrono" ] ipnetwork = [ "sqlx-core/ipnetwork", "sqlx-macros/ipnetwork" ] uuid = [ "sqlx-core/uuid", "sqlx-macros/uuid" ] -json = [ "sqlx-core/json" ] +json = [ "sqlx-core/json", "sqlx-macros/json" ] time = [ "sqlx-core/time", "sqlx-macros/time" ] [dependencies] diff --git a/sqlx-core/src/postgres/types/bigdecimal.rs b/sqlx-core/src/postgres/types/bigdecimal.rs index a17746899..47c8522ab 100644 --- a/sqlx-core/src/postgres/types/bigdecimal.rs +++ b/sqlx-core/src/postgres/types/bigdecimal.rs @@ -6,6 +6,7 @@ use num_bigint::{BigInt, Sign}; use crate::decode::Decode; use crate::encode::Encode; +use crate::postgres::protocol::TypeId; use crate::postgres::{PgData, PgRawBuffer, PgTypeInfo, PgValue, Postgres}; use crate::types::Type; @@ -17,6 +18,19 @@ impl Type for BigDecimal { } } +impl Type for [BigDecimal] { + fn type_info() -> PgTypeInfo { + PgTypeInfo::new(TypeId::ARRAY_NUMERIC, "NUMERIC[]") + // <[PgNumeric] as Type>::type_info() + } +} + +impl Type for Vec { + fn type_info() -> PgTypeInfo { + <[BigDecimal] as Type>::type_info() + } +} + impl TryFrom for PgNumeric { type Error = std::num::TryFromIntError; diff --git a/sqlx-core/src/postgres/types/ipnetwork.rs b/sqlx-core/src/postgres/types/ipnetwork.rs index db4eae177..02f227a36 100644 --- a/sqlx-core/src/postgres/types/ipnetwork.rs +++ b/sqlx-core/src/postgres/types/ipnetwork.rs @@ -36,6 +36,12 @@ impl Type for [IpNetwork] { } } +impl Type for Vec { + fn type_info() -> PgTypeInfo { + <[IpNetwork] as Type>::type_info() + } +} + impl Encode for IpNetwork { fn encode(&self, buf: &mut PgRawBuffer) { match self { diff --git a/sqlx-macros/Cargo.toml b/sqlx-macros/Cargo.toml index d5094b9c4..06e8c65ad 100644 --- a/sqlx-macros/Cargo.toml +++ b/sqlx-macros/Cargo.toml @@ -32,6 +32,7 @@ chrono = [ "sqlx/chrono" ] time = [ "sqlx/time" ] ipnetwork = [ "sqlx/ipnetwork" ] uuid = [ "sqlx/uuid" ] +json = [ "sqlx/json", "serde_json" ] [dependencies] async-std = { version = "1.5.0", default-features = false, optional = true } @@ -40,6 +41,7 @@ dotenv = { version = "0.15.0", default-features = false } futures = { version = "0.3.4", default-features = false, features = [ "executor" ] } proc-macro2 = { version = "1.0.9", default-features = false } sqlx = { version = "0.3.0-alpha.2", default-features = false, path = "../sqlx-core", package = "sqlx-core" } +serde_json = { version = "1.0", features = [ "raw_value" ], optional = true } syn = { version = "1.0.16", default-features = false, features = [ "full" ] } quote = { version = "1.0.2", default-features = false } url = { version = "2.1.1", default-features = false } diff --git a/sqlx-macros/src/database/postgres.rs b/sqlx-macros/src/database/postgres.rs index 656190a43..452ad27db 100644 --- a/sqlx-macros/src/database/postgres.rs +++ b/sqlx-macros/src/database/postgres.rs @@ -45,6 +45,9 @@ impl_database_ext! { #[cfg(feature = "ipnetwork")] sqlx::types::ipnetwork::IpNetwork, + #[cfg(feature = "json")] + serde_json::Value, + // Arrays Vec | &[bool], Vec | &[String], @@ -55,6 +58,42 @@ impl_database_ext! { Vec | &[i64], Vec | &[f32], Vec | &[f64], + + + #[cfg(feature = "uuid")] + Vec | &[sqlx::types::Uuid], + + #[cfg(feature = "chrono")] + Vec | &[sqlx::types::sqlx::types::chrono::NaiveTime], + + #[cfg(feature = "chrono")] + Vec | &[sqlx::types::chrono::NaiveDate], + + #[cfg(feature = "chrono")] + Vec | &[sqlx::types::chrono::NaiveDateTime], + + // TODO + // #[cfg(feature = "chrono")] + // Vec> | &[sqlx::types::chrono::DateTime<_>], + + #[cfg(feature = "time")] + Vec | &[sqlx::types::time::Time], + + #[cfg(feature = "time")] + Vec | &[sqlx::types::time::Date], + + #[cfg(feature = "time")] + Vec | &[sqlx::types::time::PrimitiveDateTime], + + #[cfg(feature = "time")] + Vec | &[sqlx::types::time::OffsetDateTime], + + #[cfg(feature = "bigdecimal")] + Vec | &[sqlx::types::BigDecimal], + + #[cfg(feature = "ipnetwork")] + Vec | &[sqlx::types::ipnetwork::IpNetwork], + }, ParamChecking::Strong, feature-types: info => info.type_feature_gate(), diff --git a/tests/postgres-types.rs b/tests/postgres-types.rs index 90d844ea6..70f19ee43 100644 --- a/tests/postgres-types.rs +++ b/tests/postgres-types.rs @@ -7,6 +7,30 @@ use sqlx::postgres::{PgQueryAs, PgRawBuffer, PgTypeInfo, PgValue}; use sqlx::{Cursor, Executor, Postgres, Row, Type}; use sqlx_test::{new, test_prepared_type, test_type}; +// TODO: With support for concatenation of sql literals in query! macros this should be updated +macro_rules! array_macro_test { + ($name:ident($type:ty, $($sql:literal == $value:expr),+ $(,)?)) => { + paste::item! { + #[cfg_attr(feature = "runtime-async-std", async_std::test)] + #[cfg_attr(feature = "runtime-tokio", tokio::test)] + async fn [< test_array_type_ $name >] () -> anyhow::Result<()> { + use sqlx::prelude::*; + + let mut conn = sqlx_test::new::().await?; + + $( + let v: &[$type] = $value; + let res = sqlx::query!($sql, v).fetch_one(&mut conn).await?; + assert_eq!(res.value, v); + assert_eq!(res.out, v); + )+ + + Ok(()) + } + } + }; +} + test_type!(null( Postgres, Option, @@ -19,6 +43,10 @@ test_type!(bool( "false::boolean" == false, "true::boolean" == true )); +array_macro_test!(bool( + bool, + "select '{true,false,true}'::boolean[] as value, $1::boolean[] as out" == &[true, false, true] +)); test_type!(i8(Postgres, i8, "120::\"char\"" == 120_i8)); test_type!(i16(Postgres, i16, "821::smallint" == 821_i16)); @@ -29,6 +57,10 @@ test_type!(i32( "94101::int" == 94101_i32, "-5101::int" == -5101_i32 )); +array_macro_test!(i32( + i32, + "select '{1,3,-5}'::int[] as value, $1::int[] as out" == &[1, 3, -5] +)); test_type!(u32(Postgres, u32, "94101::oid" == 94101_u32)); test_type!(i64(Postgres, i64, "9358295312::bigint" == 9358295312_i64)); @@ -39,6 +71,11 @@ test_type!(f64( f64, "939399419.1225182::double precision" == 939399419.1225182_f64 )); +array_macro_test!(f64( + f64, + "select '{939399419.1225182,-12.0}'::double precision[] as value, $1::double precision[] as out" + == &[939399419.1225182_f64, -12.0] +)); test_type!(string( Postgres, @@ -133,6 +170,32 @@ test_type!(decimal( "12345.6789::numeric" == "12345.6789".parse::().unwrap(), )); +// TODO: This is a minimal example that reproduces a typechecking error with +// arrays of BigDecimal in macros. +// +// The error is: +// error: unsupported type _NUMERIC for param #1 +// +// The implementation for bigdecimal is of the same form as all the other types. +// My (oeb25) hypothesis is that it is due to some overlap with PgNumeric, but I've been +// conclude any results. +// I have left the implementation in its ill form. It should not interfere with any of the other +// types, but it just doesn't compile if you try to use arrays of bigdecimal in query macros. + +// #[cfg(feature = "bigdecimal")] +// #[test] +// fn minimal_decimal_macro_repro() { +// use sqlx::prelude::*; +// let v: &[sqlx::types::BigDecimal] = &[]; +// sqlx::query!("select $1::numeric[] as out", v); +// } + +// array_macro_test!(decimal( +// sqlx::types::BigDecimal, +// "select '{12345.6789}'::numeric[] as value, $1::numeric[] as out" +// == &["12345.6789".parse::().unwrap()] +// )); + #[cfg(feature = "uuid")] test_type!(uuid( Postgres, @@ -142,6 +205,12 @@ test_type!(uuid( "'00000000-0000-0000-0000-000000000000'::uuid" == sqlx::types::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap() )); +#[cfg(feature = "uuid")] +array_macro_test!(uuid(sqlx::types::Uuid, "select '{b731678f-636f-4135-bc6f-19440c13bd19,00000000-0000-0000-0000-000000000000}'::uuid[] as value, $1::uuid[] as out" + == &[ + sqlx::types::Uuid::parse_str("b731678f-636f-4135-bc6f-19440c13bd19").unwrap(), + sqlx::types::Uuid::parse_str("00000000-0000-0000-0000-000000000000").unwrap() + ])); #[cfg(feature = "ipnetwork")] test_type!(ipnetwork( @@ -172,6 +241,19 @@ test_type!(ipnetwork( .parse::() .unwrap(), )); +#[cfg(feature = "ipnetwork")] +array_macro_test!(ipnetwork( + sqlx::types::ipnetwork::IpNetwork, + "select '{127.0.0.1,8.8.8.8/24}'::inet[] as value, $1::inet[] as out" + == &[ + "127.0.0.1" + .parse::() + .unwrap(), + "8.8.8.8/24" + .parse::() + .unwrap() + ] +)); #[cfg(feature = "chrono")] mod chrono { @@ -197,6 +279,11 @@ mod chrono { NaiveDateTime, "'2019-01-02 05:10:20'::timestamp" == NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20) )); + array_macro_test!(chrono_date_time( + NaiveDateTime, + "select '{2019-01-02 05:10:20}'::timestamp[] as value, $1::timestamp[] as out" + == &[NaiveDate::from_ymd(2019, 1, 2).and_hms(5, 10, 20)] + )); test_type!(chrono_date_time_tz( Postgres, @@ -207,6 +294,15 @@ mod chrono { Utc, ) )); + // TODO: Can't seem to get this to work + // array_macro_test!(chrono_date_time_tz( + // DateTime::, + // "select ARRAY[TIMESTAMPTZ '2019-01-02 05:10:20.115100'] as value, $1::TIMESTAMPTZ as out" + // == &[DateTime::::from_utc( + // NaiveDate::from_ymd(2019, 1, 2).and_hms_micro(5, 10, 20, 115100), + // Utc, + // )] + // )); } #[cfg(feature = "time")] @@ -585,4 +681,29 @@ mod json { Ok(()) } + + #[cfg_attr(feature = "runtime-async-std", async_std::test)] + #[cfg_attr(feature = "runtime-tokio", tokio::test)] + async fn test_json_value_in_macro() -> anyhow::Result<()> { + use sqlx::prelude::*; + + let mut conn = sqlx_test::new::().await?; + + let v: serde_json::Value = json!({ + "name": "Joe".to_string(), + "age": 33 + }); + + let res = sqlx::query!( + "SELECT '{\"name\":\"Joe\",\"age\":33}'::jsonb as _1, $1::jsonb as _2", + v, + ) + .fetch_one(&mut conn) + .await?; + + assert_eq!(v, res._1); + assert_eq!(res._1, res._2); + + Ok(()) + } }