Skip to content

Commit 47d3dc8

Browse files
committed
PostgreSQL interval type
Signed-off-by: itowlson <[email protected]>
1 parent 50bde9d commit 47d3dc8

File tree

5 files changed

+99
-1
lines changed

5 files changed

+99
-1
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
native-tls = "0.2"
1112
postgres-native-tls = "0.5"

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

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ 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;
8+
use tokio_postgres::types::{FromSql, Type};
99
use tokio_postgres::{config::SslMode, types::ToSql, Row};
1010
use tokio_postgres::{Client as TokioClient, NoTls, Socket};
1111

@@ -195,6 +195,7 @@ fn to_sql_parameter(value: &ParameterValue) -> Result<Box<dyn ToSql + Send + Syn
195195
ParameterValue::ArrayInt32(vs) => Ok(Box::new(vs.to_owned())),
196196
ParameterValue::ArrayInt64(vs) => Ok(Box::new(vs.to_owned())),
197197
ParameterValue::ArrayStr(vs) => Ok(Box::new(vs.to_owned())),
198+
ParameterValue::Interval(v) => Ok(Box::new(Interval(*v))),
198199
ParameterValue::DbNull => Ok(Box::new(PgNull)),
199200
}
200201
}
@@ -403,6 +404,13 @@ fn convert_entry(row: &Row, index: usize) -> anyhow::Result<DbValue> {
403404
None => DbValue::DbNull,
404405
}
405406
}
407+
&Type::INTERVAL => {
408+
let value: Option<Interval> = row.try_get(index)?;
409+
match value {
410+
Some(v) => DbValue::Interval(v.0),
411+
None => DbValue::DbNull,
412+
}
413+
}
406414
t => {
407415
tracing::debug!(
408416
"Couldn't convert Postgres type {} in column {}",
@@ -504,3 +512,80 @@ impl std::fmt::Debug for PgNull {
504512
f.debug_struct("NULL").finish()
505513
}
506514
}
515+
516+
#[derive(Debug)]
517+
struct Interval(v4::Interval);
518+
519+
impl ToSql for Interval {
520+
tokio_postgres::types::to_sql_checked!();
521+
522+
fn to_sql(
523+
&self,
524+
_ty: &Type,
525+
out: &mut tokio_postgres::types::private::BytesMut,
526+
) -> Result<tokio_postgres::types::IsNull, Box<dyn std::error::Error + Sync + Send>>
527+
where
528+
Self: Sized,
529+
{
530+
use bytes::BufMut;
531+
532+
out.put_i64(self.0.micros);
533+
out.put_i32(self.0.days);
534+
out.put_i32(self.0.months);
535+
536+
Ok(tokio_postgres::types::IsNull::No)
537+
}
538+
539+
fn accepts(ty: &Type) -> bool
540+
where
541+
Self: Sized,
542+
{
543+
matches!(ty, &Type::INTERVAL)
544+
}
545+
}
546+
547+
impl FromSql<'_> for Interval {
548+
fn from_sql(
549+
_ty: &Type,
550+
raw: &'_ [u8],
551+
) -> std::result::Result<Self, Box<dyn std::error::Error + Sync + Send>> {
552+
const EXPECTED_LEN: usize = size_of::<i64>() + size_of::<i32>() + size_of::<i32>();
553+
554+
if raw.len() != EXPECTED_LEN {
555+
return Err(Box::new(IntervalLengthError));
556+
}
557+
558+
let (micro_bytes, rest) = raw.split_at(size_of::<i64>());
559+
let (day_bytes, rest) = rest.split_at(size_of::<i32>());
560+
let month_bytes = rest;
561+
let months = i32::from_be_bytes(month_bytes.try_into().unwrap());
562+
let days = i32::from_be_bytes(day_bytes.try_into().unwrap());
563+
let micros = i64::from_be_bytes(micro_bytes.try_into().unwrap());
564+
565+
Ok(Self(v4::Interval {
566+
micros,
567+
days,
568+
months,
569+
}))
570+
}
571+
572+
fn accepts(ty: &Type) -> bool {
573+
matches!(ty, &Type::INTERVAL)
574+
}
575+
}
576+
577+
struct IntervalLengthError;
578+
579+
impl std::error::Error for IntervalLengthError {}
580+
581+
impl std::fmt::Display for IntervalLengthError {
582+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
583+
f.write_str("unexpected binary format for Postgres INTERVAL")
584+
}
585+
}
586+
587+
impl std::fmt::Debug for IntervalLengthError {
588+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
589+
std::fmt::Display::fmt(self, f)
590+
}
591+
}

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)