Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Postgres: adds SystemTime conversion from/to TIMESTAMPZ #3627

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions sqlx-postgres/src/type_checking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ impl_type_checking!(
f32,
f64,
Vec<u8> | &[u8],
std::time::SystemTime,
Copy link
Collaborator

@abonander abonander Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 This will cause the macros to prefer SystemTime *over the other types available.


sqlx::postgres::types::Oid,

Expand Down
2 changes: 2 additions & 0 deletions sqlx-postgres/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
//! | [`PgCube`] | CUBE |
//! | [`PgPoint] | POINT |
//! | [`PgHstore`] | HSTORE |
//! | [`SystemTime`](std::time::SystemTime) | TIMESTAMPTZ |
//!
//! <sup>1</sup> SQLx generally considers `CITEXT` to be compatible with `String`, `&str`, etc.,
//! but this wrapper type is available for edge cases, such as `CITEXT[]` which Postgres
Expand Down Expand Up @@ -201,6 +202,7 @@ mod oid;
mod range;
mod record;
mod str;
mod system_time;
mod text;
mod tuple;
mod void;
Expand Down
18 changes: 17 additions & 1 deletion sqlx-postgres/src/types/range.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::fmt::{self, Debug, Display, Formatter};
use std::ops::{Bound, Range, RangeBounds, RangeFrom, RangeInclusive, RangeTo, RangeToInclusive};

use std::time::SystemTime;
use bitflags::bitflags;
use sqlx_core::bytes::Buf;

Expand Down Expand Up @@ -132,6 +132,16 @@ impl Type<Postgres> for PgRange<i64> {
}
}

impl Type<Postgres> for PgRange<SystemTime> {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TSTZ_RANGE
}

fn compatible(ty: &PgTypeInfo) -> bool {
range_compatible::<SystemTime>(ty)
}
}

#[cfg(feature = "bigdecimal")]
impl Type<Postgres> for PgRange<bigdecimal::BigDecimal> {
fn type_info() -> PgTypeInfo {
Expand Down Expand Up @@ -232,6 +242,12 @@ impl PgHasArrayType for PgRange<i64> {
}
}

impl PgHasArrayType for PgRange<SystemTime> {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::TSTZ_RANGE_ARRAY
}
}

#[cfg(feature = "bigdecimal")]
impl PgHasArrayType for PgRange<bigdecimal::BigDecimal> {
fn array_type_info() -> PgTypeInfo {
Expand Down
65 changes: 65 additions & 0 deletions sqlx-postgres/src/types/system_time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use crate::decode::Decode;
use crate::encode::{Encode, IsNull};
use crate::error::BoxDynError;
use crate::types::Type;
use crate::{PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef, Postgres};
use std::time::{Duration, SystemTime};

impl Type<Postgres> for SystemTime {
fn type_info() -> PgTypeInfo {
PgTypeInfo::TIMESTAMPTZ
}
}

impl PgHasArrayType for SystemTime {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::TIMESTAMPTZ_ARRAY
}
}

impl Encode<'_, Postgres> for SystemTime {
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> {
// TIMESTAMP is encoded as the microseconds since the epoch
let micros = self.duration_since(SystemTime::UNIX_EPOCH)?.as_micros();
let micros = i64::try_from(micros)
.map_err(|_| format!("SystemTime out of range for Postgres: {self:?}"))?;
Encode::<Postgres>::encode(micros, buf)
}

fn size_hint(&self) -> usize {
size_of::<i64>()
}
}

impl<'r> Decode<'r, Postgres> for SystemTime {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
Ok(match value.format() {
PgValueFormat::Binary => {
// TIMESTAMP is encoded as the microseconds since the epoch
let us: i64 = Decode::<Postgres>::decode(value)?;
let us = u64::try_from(us)
.map_err(|_| "Postgres TIMESTAMPTZ out of range for SystemTime (SystemTime only supports timestamps after UNIX epoch)")?;
SystemTime::UNIX_EPOCH
.checked_add(Duration::from_micros(us))
.ok_or("Postgres TIMESTAMPTZ out of range for SystemTime")?
}
PgValueFormat::Text => {
// std has no datetime parsing
// We rely on chrono or time if they are available
#[cfg(feature = "chrono")]
{
chrono::DateTime::<chrono::Utc>::decode(value)?.into()
}
#[cfg(all(not(feature = "chrono"), feature = "time"))]
{
time::OffsetDateTime::decode(value)?.into()
}
#[cfg(all(not(feature = "chrono"), not(feature = "time")))]
return Err(
"not implemented: decode to SystemTime in text mode (unprepared queries)"
.into(),
);
}
})
}
}