use std::fmt::Debug;
use diesel::{
deserialize::{FromSql, FromSqlRow},
expression::{AppearsOnTable, Expression, SelectableExpression},
pg::{Pg, PgValue},
query_builder::{AstPass, QueryFragment, QueryId},
result::QueryResult,
serialize::ToSql,
sql_types::{self, BigInt, Bool, Double, Float, Integer, Nullable, SmallInt, Text},
};
use crate::{U15, U31, U63, error::InvalidArray};
#[derive(Debug, FromSqlRow)]
pub struct Array<T, const N: usize>([T; N]);
impl<T, const N: usize> Array<T, N> {
#[must_use]
pub fn new(values: [T; N]) -> Self {
Self(values)
}
#[must_use]
pub fn into_inner(self) -> [T; N] {
self.0
}
}
#[derive(Debug, FromSqlRow)]
pub struct ArrayWithNullableItems<T, const N: usize>([Option<T>; N]);
impl<T, const N: usize> ArrayWithNullableItems<T, N> {
#[must_use]
pub fn new(values: [Option<T>; N]) -> Self {
Self(values)
}
#[must_use]
pub fn into_inner(self) -> [Option<T>; N] {
self.0
}
}
macro_rules! impl_array {
(
$(
$rust_type:ident => $diesel_type:ident
),*
) => {
$(
impl<const N: usize> Expression for Array<$rust_type, N> {
type SqlType = sql_types::Array<Nullable<$diesel_type>>;
}
impl< const N: usize> QueryId for Array<$rust_type, N> {
type QueryId = <sql_types::Array<Nullable<$diesel_type>> as QueryId>::QueryId;
const HAS_STATIC_QUERY_ID: bool = <sql_types::Array<Nullable<$diesel_type>> as QueryId>::HAS_STATIC_QUERY_ID;
}
impl<const N: usize> QueryFragment<Pg> for Array<$rust_type, N>
{
fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
pass.push_bind_param(self)?;
Ok(())
}
}
impl<__QS, const N: usize> AppearsOnTable<__QS> for Array<$rust_type, N> {}
impl<__QS, const N: usize> SelectableExpression<__QS> for Array<$rust_type, N> {}
impl<const N: usize> ToSql<sql_types::Array<Nullable<$diesel_type>>, Pg> for Array<$rust_type, N>
{
fn to_sql<'b>(
&'b self,
out: &mut diesel::serialize::Output<'b, '_, Pg>,
) -> diesel::serialize::Result {
<[$rust_type] as ToSql<sql_types::Array<$diesel_type>, Pg>>::to_sql(&self.0.as_slice(), out)
}
}
impl<const N: usize> FromSql<sql_types::Array<Nullable<$diesel_type>>, Pg> for Array<$rust_type, N>
{
fn from_sql(bytes: PgValue<'_>) -> diesel::deserialize::Result<Self> {
let raw = <Vec<Option<$rust_type>> as FromSql<sql_types::Array<Nullable<$diesel_type>>, Pg>>::from_sql(bytes)?;
let res: [$rust_type; N] = raw
.into_iter()
.collect::<Option<Vec<$rust_type>>>()
.ok_or(diesel::result::Error::DeserializationError(Box::new(
InvalidArray::UnexpectedNullValue,
)))?
.try_into()
.map_err(|_| {
diesel::result::Error::DeserializationError(Box::new(
InvalidArray::UnexpectedLength,
))
})?;
Ok(Self(res))
}
}
)*
}
}
macro_rules! impl_array_with_nullable_items {
(
$(
$rust_type:ident => $diesel_type:ident
),*
) => {
$(
impl<const N: usize> Expression for ArrayWithNullableItems<$rust_type, N> {
type SqlType = sql_types::Array<Nullable<$diesel_type>>;
}
impl<const N: usize> QueryId for ArrayWithNullableItems<$rust_type, N> {
type QueryId = <sql_types::Array<Nullable<$diesel_type>> as QueryId>::QueryId;
const HAS_STATIC_QUERY_ID: bool = <sql_types::Array<Nullable<$diesel_type>> as QueryId>::HAS_STATIC_QUERY_ID;
}
impl<const N: usize> QueryFragment<Pg> for ArrayWithNullableItems<$rust_type, N>
{
fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
pass.push_bind_param(self)?;
Ok(())
}
}
impl<__QS, const N: usize> AppearsOnTable<__QS> for ArrayWithNullableItems<$rust_type, N> {}
impl<__QS, const N: usize> SelectableExpression<__QS> for ArrayWithNullableItems<$rust_type, N> {}
impl<const N: usize> ToSql<sql_types::Array<Nullable<$diesel_type>>, Pg> for ArrayWithNullableItems<$rust_type, N>
{
fn to_sql<'b>(
&'b self,
out: &mut diesel::serialize::Output<'b, '_, Pg>,
) -> diesel::serialize::Result {
<[Option<$rust_type>] as ToSql<sql_types::Array<Nullable<$diesel_type>>, Pg>>::to_sql(self.0.as_slice(), out)
}
}
impl<const N: usize> FromSql<sql_types::Array<Nullable<$diesel_type>>, Pg> for ArrayWithNullableItems<$rust_type, N>
{
fn from_sql(bytes: PgValue<'_>) -> diesel::deserialize::Result<Self> {
let raw = <Vec<Option<$rust_type>> as FromSql<sql_types::Array<Nullable<$diesel_type>>, Pg>>::from_sql(bytes)?;
let res: [Option<$rust_type>; N] = raw
.try_into()
.map_err(|_| {
diesel::result::Error::DeserializationError(Box::new(
InvalidArray::UnexpectedLength,
))
})?;
Ok(Self(res))
}
}
)*
};
}
impl_array! {
U15 => SmallInt,
U31 => Integer,
U63 => BigInt,
i16 => SmallInt,
i32 => Integer,
i64 => BigInt,
f32 => Float,
f64 => Double,
bool => Bool,
String => Text
}
impl_array_with_nullable_items! {
U15 => SmallInt,
U31 => Integer,
U63 => BigInt,
i16 => SmallInt,
i32 => Integer,
i64 => BigInt,
f32 => Float,
f64 => Double,
bool => Bool,
String => Text
}