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.
This commit is contained in:
Oliver Bøving 2020-03-29 01:28:47 +01:00 committed by GitHub
parent 0c7ab87924
commit bcb3959379
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 184 additions and 1 deletions

1
Cargo.lock generated
View File

@ -1817,6 +1817,7 @@ dependencies = [
"lazy_static",
"proc-macro2",
"quote",
"serde_json",
"sqlx-core 0.3.0-alpha.2",
"syn",
"tokio 0.2.13",

View File

@ -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]

View File

@ -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<Postgres> for BigDecimal {
}
}
impl Type<Postgres> for [BigDecimal] {
fn type_info() -> PgTypeInfo {
PgTypeInfo::new(TypeId::ARRAY_NUMERIC, "NUMERIC[]")
// <[PgNumeric] as Type<Postgres>>::type_info()
}
}
impl Type<Postgres> for Vec<BigDecimal> {
fn type_info() -> PgTypeInfo {
<[BigDecimal] as Type<Postgres>>::type_info()
}
}
impl TryFrom<BigDecimal> for PgNumeric {
type Error = std::num::TryFromIntError;

View File

@ -36,6 +36,12 @@ impl Type<Postgres> for [IpNetwork] {
}
}
impl Type<Postgres> for Vec<IpNetwork> {
fn type_info() -> PgTypeInfo {
<[IpNetwork] as Type<Postgres>>::type_info()
}
}
impl Encode<Postgres> for IpNetwork {
fn encode(&self, buf: &mut PgRawBuffer) {
match self {

View File

@ -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 }

View File

@ -45,6 +45,9 @@ impl_database_ext! {
#[cfg(feature = "ipnetwork")]
sqlx::types::ipnetwork::IpNetwork,
#[cfg(feature = "json")]
serde_json::Value,
// Arrays
Vec<bool> | &[bool],
Vec<String> | &[String],
@ -55,6 +58,42 @@ impl_database_ext! {
Vec<i64> | &[i64],
Vec<f32> | &[f32],
Vec<f64> | &[f64],
#[cfg(feature = "uuid")]
Vec<sqlx::types::Uuid> | &[sqlx::types::Uuid],
#[cfg(feature = "chrono")]
Vec<sqlx::types::chrono::NaiveTime> | &[sqlx::types::sqlx::types::chrono::NaiveTime],
#[cfg(feature = "chrono")]
Vec<sqlx::types::chrono::NaiveDate> | &[sqlx::types::chrono::NaiveDate],
#[cfg(feature = "chrono")]
Vec<sqlx::types::chrono::NaiveDateTime> | &[sqlx::types::chrono::NaiveDateTime],
// TODO
// #[cfg(feature = "chrono")]
// Vec<sqlx::types::chrono::DateTime<sqlx::types::chrono::Utc>> | &[sqlx::types::chrono::DateTime<_>],
#[cfg(feature = "time")]
Vec<sqlx::types::time::Time> | &[sqlx::types::time::Time],
#[cfg(feature = "time")]
Vec<sqlx::types::time::Date> | &[sqlx::types::time::Date],
#[cfg(feature = "time")]
Vec<sqlx::types::time::PrimitiveDateTime> | &[sqlx::types::time::PrimitiveDateTime],
#[cfg(feature = "time")]
Vec<sqlx::types::time::OffsetDateTime> | &[sqlx::types::time::OffsetDateTime],
#[cfg(feature = "bigdecimal")]
Vec<sqlx::types::BigDecimal> | &[sqlx::types::BigDecimal],
#[cfg(feature = "ipnetwork")]
Vec<sqlx::types::ipnetwork::IpNetwork> | &[sqlx::types::ipnetwork::IpNetwork],
},
ParamChecking::Strong,
feature-types: info => info.type_feature_gate(),

View File

@ -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::<Postgres>().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<i16>,
@ -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::<sqlx::types::BigDecimal>().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::<sqlx::types::BigDecimal>().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::<sqlx::types::ipnetwork::IpNetwork>()
.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::<sqlx::types::ipnetwork::IpNetwork>()
.unwrap(),
"8.8.8.8/24"
.parse::<sqlx::types::ipnetwork::IpNetwork>()
.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::<Utc>,
// "select ARRAY[TIMESTAMPTZ '2019-01-02 05:10:20.115100'] as value, $1::TIMESTAMPTZ as out"
// == &[DateTime::<Utc>::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::<Postgres>().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(())
}
}