Skip to content

Commit c640e98

Browse files
committed
PostgreSQL interval type
Signed-off-by: itowlson <[email protected]>
1 parent a9074c0 commit c640e98

File tree

5 files changed

+100
-2
lines changed

5 files changed

+100
-2
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/factor-outbound-pg/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ edition = { workspace = true }
66

77
[dependencies]
88
anyhow = { workspace = true }
9+
bytes = {workspace = true }
910
chrono = { workspace = true }
1011
deadpool-postgres = { version = "0.14", features = ["rt_tokio_1"] }
1112
moka = { version = "0.12", features = ["sync"] }

crates/factor-outbound-pg/src/client.rs

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use spin_world::async_trait;
55
use spin_world::spin::postgres4_0_0::postgres::{
66
self as v4, Column, DbDataType, DbValue, ParameterValue, RowSet,
77
};
8-
use tokio_postgres::types::Type;
9-
use tokio_postgres::{config::SslMode, types::ToSql, NoTls, Row};
8+
use tokio_postgres::types::{FromSql, ToSql, Type};
9+
use tokio_postgres::{config::SslMode, NoTls, Row};
1010

1111
/// Max connections in a given address' connection pool
1212
const CONNECTION_POOL_SIZE: usize = 64;
@@ -234,6 +234,7 @@ fn to_sql_parameter(value: &ParameterValue) -> Result<Box<dyn ToSql + Send + Syn
234234
ParameterValue::ArrayInt32(vs) => Ok(Box::new(vs.to_owned())),
235235
ParameterValue::ArrayInt64(vs) => Ok(Box::new(vs.to_owned())),
236236
ParameterValue::ArrayStr(vs) => Ok(Box::new(vs.to_owned())),
237+
ParameterValue::Interval(v) => Ok(Box::new(Interval(*v))),
237238
ParameterValue::DbNull => Ok(Box::new(PgNull)),
238239
}
239240
}
@@ -442,6 +443,13 @@ fn convert_entry(row: &Row, index: usize) -> anyhow::Result<DbValue> {
442443
None => DbValue::DbNull,
443444
}
444445
}
446+
&Type::INTERVAL => {
447+
let value: Option<Interval> = row.try_get(index)?;
448+
match value {
449+
Some(v) => DbValue::Interval(v.0),
450+
None => DbValue::DbNull,
451+
}
452+
}
445453
t => {
446454
tracing::debug!(
447455
"Couldn't convert Postgres type {} in column {}",
@@ -544,6 +552,83 @@ impl std::fmt::Debug for PgNull {
544552
}
545553
}
546554

555+
#[derive(Debug)]
556+
struct Interval(v4::Interval);
557+
558+
impl ToSql for Interval {
559+
tokio_postgres::types::to_sql_checked!();
560+
561+
fn to_sql(
562+
&self,
563+
_ty: &Type,
564+
out: &mut tokio_postgres::types::private::BytesMut,
565+
) -> Result<tokio_postgres::types::IsNull, Box<dyn std::error::Error + Sync + Send>>
566+
where
567+
Self: Sized,
568+
{
569+
use bytes::BufMut;
570+
571+
out.put_i64(self.0.micros);
572+
out.put_i32(self.0.days);
573+
out.put_i32(self.0.months);
574+
575+
Ok(tokio_postgres::types::IsNull::No)
576+
}
577+
578+
fn accepts(ty: &Type) -> bool
579+
where
580+
Self: Sized,
581+
{
582+
matches!(ty, &Type::INTERVAL)
583+
}
584+
}
585+
586+
impl FromSql<'_> for Interval {
587+
fn from_sql(
588+
_ty: &Type,
589+
raw: &'_ [u8],
590+
) -> std::result::Result<Self, Box<dyn std::error::Error + Sync + Send>> {
591+
const EXPECTED_LEN: usize = size_of::<i64>() + size_of::<i32>() + size_of::<i32>();
592+
593+
if raw.len() != EXPECTED_LEN {
594+
return Err(Box::new(IntervalLengthError));
595+
}
596+
597+
let (micro_bytes, rest) = raw.split_at(size_of::<i64>());
598+
let (day_bytes, rest) = rest.split_at(size_of::<i32>());
599+
let month_bytes = rest;
600+
let months = i32::from_be_bytes(month_bytes.try_into().unwrap());
601+
let days = i32::from_be_bytes(day_bytes.try_into().unwrap());
602+
let micros = i64::from_be_bytes(micro_bytes.try_into().unwrap());
603+
604+
Ok(Self(v4::Interval {
605+
micros,
606+
days,
607+
months,
608+
}))
609+
}
610+
611+
fn accepts(ty: &Type) -> bool {
612+
matches!(ty, &Type::INTERVAL)
613+
}
614+
}
615+
616+
struct IntervalLengthError;
617+
618+
impl std::error::Error for IntervalLengthError {}
619+
620+
impl std::fmt::Display for IntervalLengthError {
621+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
622+
f.write_str("unexpected binary format for Postgres INTERVAL")
623+
}
624+
}
625+
626+
impl std::fmt::Debug for IntervalLengthError {
627+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
628+
std::fmt::Display::fmt(self, f)
629+
}
630+
}
631+
547632
/// Workaround for moka returning Arc<Error> which, although
548633
/// necessary for concurrency, does not play well with others.
549634
struct ArcError(std::sync::Arc<anyhow::Error>);

crates/world/src/conversions.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ mod rdbms_types {
125125
pg4::DbValue::ArrayInt32(_) => pg3::DbValue::Unsupported,
126126
pg4::DbValue::ArrayInt64(_) => pg3::DbValue::Unsupported,
127127
pg4::DbValue::ArrayStr(_) => pg3::DbValue::Unsupported,
128+
pg4::DbValue::Interval(_) => pg3::DbValue::Unsupported,
128129
pg4::DbValue::DbNull => pg3::DbValue::DbNull,
129130
pg4::DbValue::Unsupported => pg3::DbValue::Unsupported,
130131
}
@@ -191,6 +192,7 @@ mod rdbms_types {
191192
pg4::DbDataType::ArrayInt32 => pg3::DbDataType::Other,
192193
pg4::DbDataType::ArrayInt64 => pg3::DbDataType::Other,
193194
pg4::DbDataType::ArrayStr => pg3::DbDataType::Other,
195+
pg4::DbDataType::Interval => pg3::DbDataType::Other,
194196
pg4::DbDataType::Other => pg3::DbDataType::Other,
195197
}
196198
}

wit/deps/[email protected]/postgres.wit

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ interface postgres {
3333
array-int32,
3434
array-int64,
3535
array-str,
36+
interval,
3637
other,
3738
}
3839

@@ -62,6 +63,7 @@ interface postgres {
6263
array-int32(list<option<s32>>),
6364
array-int64(list<option<s64>>),
6465
array-str(list<option<string>>),
66+
interval(interval),
6567
db-null,
6668
unsupported,
6769
}
@@ -92,9 +94,16 @@ interface postgres {
9294
array-int32(list<option<s32>>),
9395
array-int64(list<option<s64>>),
9496
array-str(list<option<string>>),
97+
interval(interval),
9598
db-null,
9699
}
97100

101+
record interval {
102+
micros: s64,
103+
days: s32,
104+
months: s32,
105+
}
106+
98107
/// A database column
99108
record column {
100109
name: string,

0 commit comments

Comments
 (0)