-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
feat: support jiff integration for postgresql #3511
Changes from 4 commits
f2414f8
a20b854
3e5f5d0
872812e
b26be7d
b44e935
d529261
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
use std::mem; | ||
|
||
use jiff::civil::Date; | ||
use jiff::ToSpan; | ||
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}; | ||
|
||
impl Type<Postgres> for Date { | ||
fn type_info() -> PgTypeInfo { | ||
PgTypeInfo::DATE | ||
} | ||
} | ||
|
||
impl PgHasArrayType for Date { | ||
fn array_type_info() -> PgTypeInfo { | ||
PgTypeInfo::DATE_ARRAY | ||
} | ||
} | ||
|
||
impl Encode<'_, Postgres> for Date { | ||
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { | ||
// DATE is encoded as the days since epoch | ||
let days = (*self - postgres_epoch_date()).get_days(); | ||
Encode::<Postgres>::encode(days, buf) | ||
} | ||
|
||
fn size_hint(&self) -> usize { | ||
mem::size_of::<i32>() | ||
} | ||
} | ||
|
||
impl<'r> Decode<'r, Postgres> for Date { | ||
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { | ||
Ok(match value.format() { | ||
PgValueFormat::Binary => { | ||
// DATE is encoded as the days since epoch | ||
let days: i32 = Decode::<Postgres>::decode(value)?; | ||
postgres_epoch_date() + days.days() | ||
} | ||
PgValueFormat::Text => Date::strptime("%Y-%m-%d", value.as_str()?)?, | ||
}) | ||
} | ||
} | ||
|
||
const fn postgres_epoch_date() -> Date { | ||
Date::constant(2000, 1, 1) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
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 jiff::civil::DateTime; | ||
use jiff::tz::{Offset, TimeZone}; | ||
use jiff::{SignedDuration, Timestamp, Zoned}; | ||
use std::mem; | ||
use std::str::FromStr; | ||
|
||
impl Type<Postgres> for DateTime { | ||
fn type_info() -> PgTypeInfo { | ||
PgTypeInfo::TIMESTAMP | ||
} | ||
} | ||
|
||
impl Type<Postgres> for Zoned { | ||
fn type_info() -> PgTypeInfo { | ||
PgTypeInfo::TIMESTAMPTZ | ||
} | ||
} | ||
|
||
impl PgHasArrayType for DateTime { | ||
fn array_type_info() -> PgTypeInfo { | ||
PgTypeInfo::TIMESTAMP_ARRAY | ||
} | ||
} | ||
|
||
impl PgHasArrayType for Zoned { | ||
fn array_type_info() -> PgTypeInfo { | ||
PgTypeInfo::TIMESTAMPTZ_ARRAY | ||
} | ||
} | ||
|
||
impl Encode<'_, Postgres> for DateTime { | ||
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { | ||
// TIMESTAMP is encoded as the microseconds since the epoch | ||
let micros = (*self - postgres_epoch_datetime()).get_microseconds(); | ||
Encode::<Postgres>::encode(micros, buf) | ||
} | ||
|
||
fn size_hint(&self) -> usize { | ||
mem::size_of::<i64>() | ||
} | ||
} | ||
|
||
#[derive(Debug, thiserror::Error)] | ||
#[error("error parsing datetime {squashed:?}")] | ||
struct ParseError { | ||
squashed: Vec<jiff::Error>, | ||
} | ||
|
||
impl<'r> Decode<'r, Postgres> for DateTime { | ||
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { | ||
match value.format() { | ||
PgValueFormat::Binary => { | ||
// TIMESTAMP is encoded as the microseconds since the epoch | ||
let us = Decode::<Postgres>::decode(value)?; | ||
Ok(postgres_epoch_datetime() + SignedDuration::from_micros(us)) | ||
} | ||
PgValueFormat::Text => { | ||
let input = value.as_str()?; | ||
let mut squashed = vec![]; | ||
match DateTime::strptime("%Y-%m-%d %H:%M:%S%.f", input) { | ||
Ok(datetime) => return Ok(datetime), | ||
Err(err) => squashed.push(err), | ||
} | ||
match DateTime::strptime("%Y-%m-%d %H:%M:%S%.f%#z", input) { | ||
Ok(datetime) => return Ok(datetime), | ||
Err(err) => squashed.push(err), | ||
} | ||
Err(Box::new(ParseError { squashed })) | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl Encode<'_, Postgres> for Timestamp { | ||
fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result<IsNull, BoxDynError> { | ||
let datetime = Offset::UTC.to_datetime(*self); | ||
Encode::<Postgres>::encode(datetime, buf) | ||
} | ||
|
||
fn size_hint(&self) -> usize { | ||
mem::size_of::<i64>() | ||
} | ||
} | ||
|
||
impl<'r> Decode<'r, Postgres> for Timestamp { | ||
fn decode(value: PgValueRef<'r>) -> Result<Self, BoxDynError> { | ||
Ok(match value.format() { | ||
PgValueFormat::Binary => { | ||
let naive = <DateTime as Decode<Postgres>>::decode(value)?; | ||
naive.to_zoned(TimeZone::UTC)?.timestamp() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @BurntSushi Is this the proper way to convert DateTime to Timestamp? I don't find a direct way though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's no direct way because there is no such thing as a general way of converting a civil datetime to a timestamp. The former is naive or civil or local time, where as the latter is a precise instant in time. The only way to convert the former to the latter is with an offset. In this particular case, you've chose an offset of I think you have things backwards here. Then have a different function that returns the base for civil time. See https://github.com/sfackler/rust-postgres/pull/1164/files for how it's done there. You should be able to copy that same conceptual approach. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes correct. Thank you. |
||
} | ||
PgValueFormat::Text => Timestamp::from_str(value.as_str()?)?, | ||
}) | ||
} | ||
} | ||
|
||
const fn postgres_epoch_datetime() -> DateTime { | ||
DateTime::constant(2000, 1, 1, 0, 0, 0, 0) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
mod date; | ||
mod datetime; | ||
mod time; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect arith, should probably use: