Skip to content

Commit 1d7c700

Browse files
committed
Unit tests, PG4 feature
Signed-off-by: itowlson <[email protected]>
1 parent a4a6b9e commit 1d7c700

File tree

2 files changed

+172
-5
lines changed

2 files changed

+172
-5
lines changed

Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ anyhow = "1"
2121
async-trait = "0.1.74"
2222
chrono = "0.4.38"
2323
form_urlencoded = "1.0"
24-
rust_decimal = { version = "1.37.2", default-features = false }
24+
postgres_range = { version = "0.11.1", optional = true }
25+
rust_decimal = { version = "1.37.2", default-features = false, optional = true }
2526
spin-executor = { version = "4.0.0", path = "crates/executor" }
2627
spin-macro = { version = "4.0.0", path = "crates/macro" }
2728
thiserror = "1.0.37"
29+
uuid = { version = "1.18.0", optional = true }
2830
wit-bindgen = { workspace = true }
2931
routefinder = "0.5.3"
3032
once_cell = { workspace = true }
@@ -34,13 +36,12 @@ hyperium = { package = "http", version = "1.0.0" }
3436
serde_json = { version = "1.0.96", optional = true }
3537
serde = { version = "1.0.163", optional = true }
3638
wasi = { workspace = true }
37-
uuid = "1.18.0"
38-
postgres_range = "0.11.1"
3939

4040
[features]
41-
default = ["export-sdk-language", "json"]
41+
default = ["export-sdk-language", "json", "postgres4-types"]
4242
export-sdk-language = []
4343
json = ["dep:serde", "dep:serde_json"]
44+
postgres4-types = ["dep:rust_decimal", "dep:uuid", "dep:postgres_range", "json"]
4445

4546
[workspace]
4647
resolver = "2"

src/pg4.rs

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ impl Decode for chrono::Duration {
328328
}
329329
}
330330

331+
#[cfg(feature = "postgres4-types")]
331332
impl Decode for uuid::Uuid {
332333
fn decode(value: &DbValue) -> Result<Self, Error> {
333334
match value {
@@ -337,20 +338,23 @@ impl Decode for uuid::Uuid {
337338
}
338339
}
339340

341+
#[cfg(feature = "json")]
340342
impl Decode for serde_json::Value {
341343
fn decode(value: &DbValue) -> Result<Self, Error> {
342344
from_jsonb(value)
343345
}
344346
}
345347

346348
/// Convert a Postgres JSONB value to a `Deserialize`-able type.
349+
#[cfg(feature = "json")]
347350
pub fn from_jsonb<'a, T: serde::Deserialize<'a>>(value: &'a DbValue) -> Result<T, Error> {
348351
match value {
349352
DbValue::Jsonb(j) => serde_json::from_slice(j).map_err(|e| Error::Decode(e.to_string())),
350353
_ => Err(Error::Decode(format_decode_err("JSONB", value))),
351354
}
352355
}
353356

357+
#[cfg(feature = "postgres4-types")]
354358
impl Decode for rust_decimal::Decimal {
355359
fn decode(value: &DbValue) -> Result<Self, Error> {
356360
match value {
@@ -362,13 +366,15 @@ impl Decode for rust_decimal::Decimal {
362366
}
363367
}
364368

369+
#[cfg(feature = "postgres4-types")]
365370
fn bound_type_from_wit(kind: RangeBoundKind) -> postgres_range::BoundType {
366371
match kind {
367372
RangeBoundKind::Inclusive => postgres_range::BoundType::Inclusive,
368373
RangeBoundKind::Exclusive => postgres_range::BoundType::Exclusive,
369374
}
370375
}
371376

377+
#[cfg(feature = "postgres4-types")]
372378
impl Decode for postgres_range::Range<i32> {
373379
fn decode(value: &DbValue) -> Result<Self, Error> {
374380
match value {
@@ -386,6 +392,7 @@ impl Decode for postgres_range::Range<i32> {
386392
}
387393
}
388394

395+
#[cfg(feature = "postgres4-types")]
389396
impl Decode for postgres_range::Range<i64> {
390397
fn decode(value: &DbValue) -> Result<Self, Error> {
391398
match value {
@@ -403,7 +410,41 @@ impl Decode for postgres_range::Range<i64> {
403410
}
404411
}
405412

406-
// TODO: NUMERICRANGE
413+
// We can't use postgres_range::Range because rust_decimal::Decimal
414+
// is not Normalizable
415+
#[cfg(feature = "postgres4-types")]
416+
impl Decode
417+
for (
418+
Option<(rust_decimal::Decimal, RangeBoundKind)>,
419+
Option<(rust_decimal::Decimal, RangeBoundKind)>,
420+
)
421+
{
422+
fn decode(value: &DbValue) -> Result<Self, Error> {
423+
fn parse(
424+
value: &str,
425+
kind: RangeBoundKind,
426+
) -> Result<(rust_decimal::Decimal, RangeBoundKind), Error> {
427+
let dec = rust_decimal::Decimal::from_str_exact(value)
428+
.map_err(|e| Error::Decode(e.to_string()))?;
429+
Ok((dec, kind))
430+
}
431+
432+
match value {
433+
DbValue::RangeDecimal((lbound, ubound)) => {
434+
let lower = lbound
435+
.as_ref()
436+
.map(|(value, kind)| parse(value, *kind))
437+
.transpose()?;
438+
let upper = ubound
439+
.as_ref()
440+
.map(|(value, kind)| parse(value, *kind))
441+
.transpose()?;
442+
Ok((lower, upper))
443+
}
444+
_ => Err(Error::Decode(format_decode_err("NUMERICRANGE", value))),
445+
}
446+
}
447+
}
407448

408449
// TODO: can we return a slice here? It seems like it should be possible but
409450
// I wasn't able to get the lifetimes to work with the trait
@@ -434,13 +475,15 @@ impl Decode for Vec<Option<String>> {
434475
}
435476
}
436477

478+
#[cfg(feature = "postgres4-types")]
437479
fn map_decimal(s: &Option<String>) -> Result<Option<rust_decimal::Decimal>, Error> {
438480
s.as_ref()
439481
.map(|s| rust_decimal::Decimal::from_str_exact(s))
440482
.transpose()
441483
.map_err(|e| Error::Decode(e.to_string()))
442484
}
443485

486+
#[cfg(feature = "postgres4-types")]
444487
impl Decode for Vec<Option<rust_decimal::Decimal>> {
445488
fn decode(value: &DbValue) -> Result<Self, Error> {
446489
match value {
@@ -526,12 +569,14 @@ impl From<chrono::TimeDelta> for ParameterValue {
526569
}
527570
}
528571

572+
#[cfg(feature = "postgres4-types")]
529573
impl From<uuid::Uuid> for ParameterValue {
530574
fn from(v: uuid::Uuid) -> ParameterValue {
531575
ParameterValue::Uuid(v.to_string())
532576
}
533577
}
534578

579+
#[cfg(feature = "json")]
535580
impl TryFrom<serde_json::Value> for ParameterValue {
536581
type Error = serde_json::Error;
537582

@@ -541,11 +586,13 @@ impl TryFrom<serde_json::Value> for ParameterValue {
541586
}
542587

543588
/// Converts a `Serialize` value to a Postgres JSONB SQL parameter.
589+
#[cfg(feature = "json")]
544590
pub fn jsonb<T: serde::Serialize>(value: &T) -> Result<ParameterValue, serde_json::Error> {
545591
let json = serde_json::to_vec(value)?;
546592
Ok(ParameterValue::Jsonb(json))
547593
}
548594

595+
#[cfg(feature = "postgres4-types")]
549596
impl From<rust_decimal::Decimal> for ParameterValue {
550597
fn from(v: rust_decimal::Decimal) -> ParameterValue {
551598
ParameterValue::Decimal(v.to_string())
@@ -580,6 +627,7 @@ fn range_bound_to_wit<T, U>(
580627
}
581628
}
582629

630+
#[cfg(feature = "postgres4-types")]
583631
fn pg_range_bound_to_wit<S: postgres_range::BoundSided, T: Copy>(
584632
bound: &postgres_range::RangeBound<S, T>,
585633
) -> (T, RangeBoundKind) {
@@ -620,6 +668,7 @@ impl From<std::ops::RangeToInclusive<i32>> for ParameterValue {
620668
}
621669
}
622670

671+
#[cfg(feature = "postgres4-types")]
623672
impl From<postgres_range::Range<i32>> for ParameterValue {
624673
fn from(v: postgres_range::Range<i32>) -> ParameterValue {
625674
let lbound = v.lower().map(pg_range_bound_to_wit);
@@ -658,6 +707,7 @@ impl From<std::ops::RangeToInclusive<i64>> for ParameterValue {
658707
}
659708
}
660709

710+
#[cfg(feature = "postgres4-types")]
661711
impl From<postgres_range::Range<i64>> for ParameterValue {
662712
fn from(v: postgres_range::Range<i64>) -> ParameterValue {
663713
let lbound = v.lower().map(pg_range_bound_to_wit);
@@ -666,6 +716,7 @@ impl From<postgres_range::Range<i64>> for ParameterValue {
666716
}
667717
}
668718

719+
#[cfg(feature = "postgres4-types")]
669720
impl From<std::ops::Range<rust_decimal::Decimal>> for ParameterValue {
670721
fn from(v: std::ops::Range<rust_decimal::Decimal>) -> ParameterValue {
671722
ParameterValue::RangeDecimal(range_bounds_to_wit(v, |d| d.to_string()))
@@ -690,6 +741,7 @@ impl From<Vec<String>> for ParameterValue {
690741
}
691742
}
692743

744+
#[cfg(feature = "postgres4-types")]
693745
impl From<Vec<Option<rust_decimal::Decimal>>> for ParameterValue {
694746
fn from(v: Vec<Option<rust_decimal::Decimal>>) -> ParameterValue {
695747
let strs = v
@@ -700,6 +752,7 @@ impl From<Vec<Option<rust_decimal::Decimal>>> for ParameterValue {
700752
}
701753
}
702754

755+
#[cfg(feature = "postgres4-types")]
703756
impl From<Vec<rust_decimal::Decimal>> for ParameterValue {
704757
fn from(v: Vec<rust_decimal::Decimal>) -> ParameterValue {
705758
let strs = v.into_iter().map(|d| Some(d.to_string())).collect();
@@ -859,4 +912,117 @@ mod tests {
859912
.unwrap()
860913
.is_none());
861914
}
915+
916+
#[test]
917+
#[cfg(feature = "postgres4-types")]
918+
fn uuid() {
919+
let uuid_str = "12341234-1234-1234-1234-123412341234";
920+
assert_eq!(
921+
uuid::Uuid::try_parse(uuid_str).unwrap(),
922+
uuid::Uuid::decode(&DbValue::Uuid(uuid_str.to_owned())).unwrap(),
923+
);
924+
assert!(Option::<uuid::Uuid>::decode(&DbValue::DbNull)
925+
.unwrap()
926+
.is_none());
927+
}
928+
929+
#[derive(Debug, serde::Deserialize, PartialEq)]
930+
struct JsonTest {
931+
hello: String,
932+
}
933+
934+
#[test]
935+
#[cfg(feature = "json")]
936+
fn jsonb() {
937+
let json_val = serde_json::json!({
938+
"hello": "world"
939+
});
940+
let dbval = DbValue::Jsonb(r#"{"hello":"world"}"#.into());
941+
942+
assert_eq!(json_val, serde_json::Value::decode(&dbval).unwrap(),);
943+
944+
let json_struct = JsonTest {
945+
hello: "world".to_owned(),
946+
};
947+
assert_eq!(json_struct, from_jsonb(&dbval).unwrap());
948+
}
949+
950+
#[test]
951+
#[cfg(feature = "postgres4-types")]
952+
fn ranges() {
953+
let i32_range = postgres_range::Range::<i32>::decode(&DbValue::RangeInt32((
954+
Some((45, RangeBoundKind::Inclusive)),
955+
Some((89, RangeBoundKind::Exclusive)),
956+
)))
957+
.unwrap();
958+
assert_eq!(45, i32_range.lower().unwrap().value);
959+
assert_eq!(
960+
postgres_range::BoundType::Inclusive,
961+
i32_range.lower().unwrap().type_
962+
);
963+
assert_eq!(89, i32_range.upper().unwrap().value);
964+
assert_eq!(
965+
postgres_range::BoundType::Exclusive,
966+
i32_range.upper().unwrap().type_
967+
);
968+
969+
let i32_range_from = postgres_range::Range::<i32>::decode(&DbValue::RangeInt32((
970+
Some((45, RangeBoundKind::Inclusive)),
971+
None,
972+
)))
973+
.unwrap();
974+
assert!(i32_range_from.upper().is_none());
975+
976+
let i64_range = postgres_range::Range::<i64>::decode(&DbValue::RangeInt64((
977+
Some((4567456745674567, RangeBoundKind::Inclusive)),
978+
Some((890189018901890189, RangeBoundKind::Exclusive)),
979+
)))
980+
.unwrap();
981+
assert_eq!(4567456745674567, i64_range.lower().unwrap().value);
982+
assert_eq!(890189018901890189, i64_range.upper().unwrap().value);
983+
984+
let (dec_lbound, dec_ubound): (
985+
Option<(rust_decimal::Decimal, RangeBoundKind)>,
986+
Option<(rust_decimal::Decimal, RangeBoundKind)>,
987+
) = Decode::decode(&DbValue::RangeDecimal((
988+
Some(("4567.8901".to_owned(), RangeBoundKind::Inclusive)),
989+
Some(("8901.2345678901".to_owned(), RangeBoundKind::Exclusive)),
990+
)))
991+
.unwrap();
992+
assert_eq!(
993+
rust_decimal::Decimal::from_i128_with_scale(45678901, 4),
994+
dec_lbound.unwrap().0
995+
);
996+
assert_eq!(
997+
rust_decimal::Decimal::from_i128_with_scale(89012345678901, 10),
998+
dec_ubound.unwrap().0
999+
);
1000+
}
1001+
1002+
#[test]
1003+
#[cfg(feature = "postgres4-types")]
1004+
fn arrays() {
1005+
let v32 = vec![Some(123), None, Some(456)];
1006+
let i32_arr = Vec::<Option<i32>>::decode(&DbValue::ArrayInt32(v32.clone())).unwrap();
1007+
assert_eq!(v32, i32_arr);
1008+
1009+
let v64 = vec![Some(123), None, Some(456)];
1010+
let i64_arr = Vec::<Option<i64>>::decode(&DbValue::ArrayInt64(v64.clone())).unwrap();
1011+
assert_eq!(v64, i64_arr);
1012+
1013+
let vdec = vec![Some("1.23".to_owned()), None];
1014+
let dec_arr =
1015+
Vec::<Option<rust_decimal::Decimal>>::decode(&DbValue::ArrayDecimal(vdec)).unwrap();
1016+
assert_eq!(
1017+
vec![
1018+
Some(rust_decimal::Decimal::from_i128_with_scale(123, 2)),
1019+
None
1020+
],
1021+
dec_arr
1022+
);
1023+
1024+
let vstr = vec![Some("alice".to_owned()), None, Some("bob".to_owned())];
1025+
let str_arr = Vec::<Option<String>>::decode(&DbValue::ArrayStr(vstr.clone())).unwrap();
1026+
assert_eq!(vstr, str_arr);
1027+
}
8621028
}

0 commit comments

Comments
 (0)