use bytes::{BufMut, Bytes, BytesMut};
use postgres_types::{ToSql, Type};
use rw_iter_util::ZipEqFast;
use super::{
DataType, Date, Decimal, Interval, ScalarRefImpl, Serial, Time, Timestamp, Timestamptz, F32,
F64,
};
use crate::array::{ListRef, StructRef};
use crate::error::NotImplemented;
#[derive(thiserror::Error, Debug)]
pub enum ToBinaryError {
#[error(transparent)]
ToSql(Box<dyn std::error::Error + Send + Sync>),
#[error(transparent)]
NotImplemented(#[from] NotImplemented),
}
pub type Result<T> = std::result::Result<T, ToBinaryError>;
pub trait ToBinary {
fn to_binary_with_type(&self, ty: &DataType) -> Result<Bytes>;
}
macro_rules! implement_using_to_sql {
($({ $scalar_type:ty, $data_type:ident, $accessor:expr } ),* $(,)?) => {
$(
impl ToBinary for $scalar_type {
fn to_binary_with_type(&self, ty: &DataType) -> Result<Bytes> {
match ty {
DataType::$data_type => {
let mut output = BytesMut::new();
#[allow(clippy::redundant_closure_call)]
$accessor(self).to_sql(&Type::ANY, &mut output).map_err(ToBinaryError::ToSql)?;
Ok(output.freeze())
},
_ => unreachable!(),
}
}
}
)*
};
}
implement_using_to_sql! {
{ i16, Int16, |x| x },
{ i32, Int32, |x| x },
{ i64, Int64, |x| x },
{ &str, Varchar, |x| x },
{ F32, Float32, |x: &F32| x.0 },
{ F64, Float64, |x: &F64| x.0 },
{ bool, Boolean, |x| x },
{ &[u8], Bytea, |x| x },
{ Time, Time, |x: &Time| x.0 },
{ Date, Date, |x: &Date| x.0 },
{ Timestamp, Timestamp, |x: &Timestamp| x.0 },
{ Decimal, Decimal, |x| x },
{ Interval, Interval, |x| x },
{ Serial, Serial, |x: &Serial| x.0 },
{ Timestamptz, Timestamptz, |x: &Timestamptz| x.to_datetime_utc() }
}
impl ToBinary for ListRef<'_> {
fn to_binary_with_type(&self, ty: &DataType) -> Result<Bytes> {
use crate::row::Row;
let element_ty = match ty {
DataType::List(ty) => ty.as_ref(),
_ => unreachable!(),
};
if matches!(element_ty, DataType::List(_)) {
bail_not_implemented!(
issue = 7949,
"list with 2 or more dimensions is not supported"
)
}
let mut buf = BytesMut::new();
buf.put_i32(1); buf.put_i32(1); buf.put_i32(element_ty.to_oid()); buf.put_i32(self.len() as i32); buf.put_i32(0); for element in self.iter() {
match element {
None => {
buf.put_i32(-1); }
Some(value) => {
let data = value.to_binary_with_type(element_ty)?;
buf.put_i32(data.len() as i32); buf.put(data);
}
}
}
Ok(buf.into())
}
}
impl ToBinary for StructRef<'_> {
fn to_binary_with_type(&self, ty: &DataType) -> Result<Bytes> {
let mut buf = BytesMut::new();
buf.put_i32(ty.as_struct().len() as i32); for (datum, field_ty) in self.iter_fields_ref().zip_eq_fast(ty.as_struct().types()) {
buf.put_i32(field_ty.to_oid()); match datum {
None => {
buf.put_i32(-1); }
Some(value) => {
let data = value.to_binary_with_type(field_ty)?;
buf.put_i32(data.len() as i32); buf.put(data);
}
}
}
Ok(buf.into())
}
}
impl ToBinary for ScalarRefImpl<'_> {
fn to_binary_with_type(&self, ty: &DataType) -> Result<Bytes> {
match self {
ScalarRefImpl::Int16(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Int32(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Int64(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Int256(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Serial(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Float32(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Float64(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Utf8(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Bool(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Decimal(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Interval(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Date(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Timestamp(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Timestamptz(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Time(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Bytea(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Jsonb(v) => v.to_binary_with_type(ty),
ScalarRefImpl::List(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Struct(v) => v.to_binary_with_type(ty),
ScalarRefImpl::Map(_) => {
bail_not_implemented!(
issue = 7949,
"the pgwire extended-mode encoding for {ty} is unsupported"
)
}
}
}
}