use postgres_types::FromSql;
use std::fmt::Display;
use thiserror::Error;
use tokio_postgres::{error::Error as SqlError, row::RowIndex, Column};
#[derive(Debug, Error)]
pub enum Error {
#[error("{msg}")]
Custom { msg: String },
#[error("invalid number of columns, found {found} but expected {expected}")]
ColumnCount { found: usize, expected: usize },
#[error("internal postgres error")]
Sql(#[from] SqlError),
}
impl Error {
pub fn new<D>(msg: D) -> Error
where
D: Display,
{
Error::Custom {
msg: msg.to_string(),
}
}
}
pub trait Row {
fn columns(&self) -> &[Column];
fn try_get<'a, I, T>(&'a self, index: I) -> Result<T, Error>
where
I: RowIndex + Display,
T: FromSql<'a>;
fn len(&self) -> usize {
self.columns().len()
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn get<'a, I, T>(&'a self, index: I) -> T
where
I: RowIndex + Display,
T: FromSql<'a>,
{
match self.try_get::<I, T>(index) {
Ok(value) => value,
Err(err) => panic!("failed to retrieve column: {}", err),
}
}
}
pub trait FromSqlRow: Sized {
fn from_row<R>(row: &R) -> Result<Self, Error>
where
R: Row;
fn from_row_multi<R>(rows: &[R]) -> Result<Vec<Self>, Error>
where
R: Row,
{
rows.iter().map(Self::from_row).collect()
}
}
impl Row for tokio_postgres::Row {
fn columns(&self) -> &[Column] {
tokio_postgres::Row::columns(self)
}
fn try_get<'a, I, T>(&'a self, index: I) -> Result<T, Error>
where
I: RowIndex + Display,
T: FromSql<'a>,
{
tokio_postgres::Row::try_get(self, index).map_err(Error::from)
}
fn len(&self) -> usize {
tokio_postgres::Row::len(self)
}
fn is_empty(&self) -> bool {
tokio_postgres::Row::is_empty(self)
}
fn get<'a, I, T>(&'a self, index: I) -> T
where
I: RowIndex + Display,
T: FromSql<'a>,
{
tokio_postgres::Row::get(self, index)
}
}
macro_rules! impl_from_row_for_tuple {
(($($elem:ident),+)) => {
impl<$($elem),+> FromSqlRow for ($($elem,)+)
where $($elem: for<'a> FromSql<'a> + std::fmt::Display),+
{
fn from_row<R>(row: &R) -> Result<Self, Error>
where R: Row {
const EXPECTED: usize = impl_from_row_for_tuple!(@count ($($elem),*));
if row.len() != EXPECTED {
Err(Error::ColumnCount {
expected: EXPECTED,
found: row.len(),
})
} else {
let result = (
$(
row.try_get::<usize, $elem>(
impl_from_row_for_tuple!(@index $elem)
)?,
)+
);
Ok(result)
}
}
}
};
(@index A) => { 0 };
(@index B) => { 1 };
(@index C) => { 2 };
(@index D) => { 3 };
(@index E) => { 4 };
(@index F) => { 5 };
(@index G) => { 6 };
(@index H) => { 7 };
(@count ()) => { 0 };
(@count ($head:ident $(, $tail:ident)*)) => {{
1 + impl_from_row_for_tuple!(@count ($($tail),*))
}};
}
impl_from_row_for_tuple!((A));
impl_from_row_for_tuple!((A, B));
impl_from_row_for_tuple!((A, B, C));
impl_from_row_for_tuple!((A, B, C, D));
impl_from_row_for_tuple!((A, B, C, D, E));
impl_from_row_for_tuple!((A, B, C, D, E, F));
impl_from_row_for_tuple!((A, B, C, D, E, F, G));
impl_from_row_for_tuple!((A, B, C, D, E, F, G, H));