use jiff::{civil, tz};
use sqlx_core::{
decode::Decode,
encode::{Encode, IsNull},
error::BoxDynError,
types::Type,
};
use sqlx_postgres::{
types::{Oid, PgInterval},
PgArgumentBuffer, PgHasArrayType, PgTypeInfo, PgValueFormat, PgValueRef,
Postgres,
};
use crate::{Date, DateTime, Span, Time, Timestamp, ToSqlx};
static POSTGRES_EPOCH_DATE: civil::Date = civil::date(2000, 1, 1);
static POSTGRES_EPOCH_DATETIME: civil::DateTime =
civil::date(2000, 1, 1).at(0, 0, 0, 0);
static POSTGRES_EPOCH_TIMESTAMP: i64 = 946684800;
static MIDNIGHT: civil::Time = civil::Time::midnight();
static UTC: tz::TimeZone = tz::TimeZone::UTC;
impl Type<Postgres> for Timestamp {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1184))
}
}
impl PgHasArrayType for Timestamp {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1185))
}
}
impl Encode<'_, Postgres> for Timestamp {
fn encode_by_ref(
&self,
buf: &mut PgArgumentBuffer,
) -> Result<IsNull, BoxDynError> {
let dt = UTC.to_datetime(self.to_jiff()).to_sqlx();
Encode::<Postgres>::encode(dt, buf)
}
}
impl<'r> Decode<'r, Postgres> for Timestamp {
fn decode(value: PgValueRef<'r>) -> Result<Timestamp, BoxDynError> {
match value.format() {
PgValueFormat::Binary => {
let micros: i64 = Decode::<Postgres>::decode(value)?;
let micros = jiff::SignedDuration::from_micros(micros);
let epoch =
jiff::Timestamp::from_second(POSTGRES_EPOCH_TIMESTAMP)
.unwrap();
Ok(epoch.checked_add(micros)?.to_sqlx())
}
PgValueFormat::Text => {
Ok(value.as_str()?.parse::<jiff::Timestamp>()?.to_sqlx())
}
}
}
}
impl Type<Postgres> for DateTime {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1114))
}
}
impl PgHasArrayType for DateTime {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1115))
}
}
impl Encode<'_, Postgres> for DateTime {
fn encode_by_ref(
&self,
buf: &mut PgArgumentBuffer,
) -> Result<IsNull, BoxDynError> {
let micros =
self.to_jiff().duration_since(POSTGRES_EPOCH_DATETIME).as_micros();
let micros = i64::try_from(micros).unwrap();
Encode::<Postgres>::encode(micros, buf)
}
}
impl<'r> Decode<'r, Postgres> for DateTime {
fn decode(value: PgValueRef<'r>) -> Result<DateTime, BoxDynError> {
match value.format() {
PgValueFormat::Binary => {
let micros: i64 = Decode::<Postgres>::decode(value)?;
let micros = jiff::SignedDuration::from_micros(micros);
Ok(POSTGRES_EPOCH_DATETIME.checked_add(micros)?.to_sqlx())
}
PgValueFormat::Text => {
Ok(value.as_str()?.parse::<civil::DateTime>()?.to_sqlx())
}
}
}
}
impl Type<Postgres> for Date {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1082))
}
}
impl PgHasArrayType for Date {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1182))
}
}
impl Encode<'_, Postgres> for Date {
fn encode_by_ref(
&self,
buf: &mut PgArgumentBuffer,
) -> Result<IsNull, BoxDynError> {
let days = (self.to_jiff() - POSTGRES_EPOCH_DATE).get_days();
Encode::<Postgres>::encode(days, buf)
}
}
impl<'r> Decode<'r, Postgres> for Date {
fn decode(value: PgValueRef<'r>) -> Result<Date, BoxDynError> {
match value.format() {
PgValueFormat::Binary => {
let days: i32 = Decode::<Postgres>::decode(value)?;
let span = jiff::Span::new().try_days(days)?;
Ok(POSTGRES_EPOCH_DATE.checked_add(span)?.to_sqlx())
}
PgValueFormat::Text => {
Ok(value.as_str()?.parse::<civil::Date>()?.to_sqlx())
}
}
}
}
impl Type<Postgres> for Time {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1083))
}
}
impl PgHasArrayType for Time {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1183))
}
}
impl Encode<'_, Postgres> for Time {
fn encode_by_ref(
&self,
buf: &mut PgArgumentBuffer,
) -> Result<IsNull, BoxDynError> {
let micros = self.to_jiff().duration_since(MIDNIGHT).as_micros();
let micros = i64::try_from(micros).unwrap();
Encode::<Postgres>::encode(micros, buf)
}
}
impl<'r> Decode<'r, Postgres> for Time {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
match value.format() {
PgValueFormat::Binary => {
let micros: i64 = Decode::<Postgres>::decode(value)?;
let micros = jiff::SignedDuration::from_micros(micros);
Ok(MIDNIGHT.checked_add(micros)?.to_sqlx())
}
PgValueFormat::Text => {
Ok(value.as_str()?.parse::<civil::Time>()?.to_sqlx())
}
}
}
}
impl Type<Postgres> for Span {
fn type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1186))
}
}
impl PgHasArrayType for Span {
fn array_type_info() -> PgTypeInfo {
PgTypeInfo::with_oid(Oid(1187))
}
}
impl<'r> Decode<'r, Postgres> for Span {
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> {
let interval: PgInterval = Decode::<Postgres>::decode(value)?;
let span = jiff::Span::new()
.try_months(interval.months)?
.try_days(interval.days)?
.try_microseconds(interval.microseconds)?;
Ok(span.to_sqlx())
}
}