Skip to content

Commit a333716

Browse files
committed
wip
1 parent 9d4832b commit a333716

File tree

4 files changed

+271
-2
lines changed

4 files changed

+271
-2
lines changed

benzina/Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@ rustc-hash = { version = "2.1.1", optional = true }
3434
[dev-dependencies]
3535
serde = "1.0.221"
3636
serde_test = "1"
37-
uuid = { version = ">=0.7.0, <2.0.0", default-features = false, features = ["v4"] }
37+
uuid = { version = ">=0.7.0, <2.0.0", default-features = false, features = [
38+
"v4",
39+
] }
3840

3941
[features]
40-
default = ["derive"]
42+
default = ["derive", "json", "array"]
4143
derive = ["dep:benzina-derive", "dep:diesel", "dep:indexmap"]
4244
rustc-hash = ["dep:rustc-hash"]
4345

benzina/src/array.rs

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
use std::fmt::Debug;
2+
3+
use diesel::{
4+
deserialize::{FromSql, FromSqlRow},
5+
expression::{AppearsOnTable, Expression, SelectableExpression},
6+
pg::{Pg, PgValue},
7+
query_builder::{AstPass, QueryFragment, QueryId},
8+
result::QueryResult,
9+
serialize::ToSql,
10+
sql_types::{
11+
self, BigInt, Bool, Double, Float, Integer, Json as DbJson, Jsonb as DbJsonb, Nullable,
12+
SmallInt, Text,
13+
},
14+
};
15+
use serde::{Serialize, de::DeserializeOwned};
16+
17+
use crate::{Json, Jsonb, U15, U31, U63, error::InvalidArray};
18+
19+
/// A diesel [Array](diesel::pg::sql_types::Array) serialization and deserialization wrapper that enforces every item is not null
20+
///
21+
/// See [`array_wrapped`](crate::array_wrapped) for an usage example.
22+
#[derive(Debug, FromSqlRow)]
23+
pub struct Array<T, const N: usize>([T; N]);
24+
impl<T, const N: usize> Array<T, N> {
25+
pub fn new(values: [T; N]) -> Self {
26+
Self(values)
27+
}
28+
29+
pub fn get(self) -> [T; N] {
30+
self.0
31+
}
32+
}
33+
34+
impl<T, const N: usize> From<[T; N]> for Array<T, N> {
35+
fn from(values: [T; N]) -> Self {
36+
Self(values)
37+
}
38+
}
39+
40+
impl<T, const N: usize> From<Array<T, N>> for [T; N] {
41+
fn from(value: Array<T, N>) -> Self {
42+
value.0
43+
}
44+
}
45+
46+
/// A diesel [Array](diesel::pg::sql_types::Array) serialization and deserialization wrapper
47+
///
48+
/// See [`array_wrapped`](crate::array_wrapped) for an usage example.
49+
#[derive(Debug, FromSqlRow)]
50+
pub struct NullableArray<T, const N: usize>([Option<T>; N]);
51+
impl<T, const N: usize> NullableArray<T, N> {
52+
pub fn new(values: [Option<T>; N]) -> Self {
53+
Self(values)
54+
}
55+
56+
pub fn get(self) -> [Option<T>; N] {
57+
self.0
58+
}
59+
}
60+
61+
macro_rules! d {
62+
() => {
63+
64+
};
65+
}
66+
trait IntoNullableArray<T, const N: usize> {
67+
fn into_nullable_array(self) -> NullableArray<T, N>;
68+
}
69+
trait FromNullableArray<T, const N: usize> {
70+
fn from_nullable_array(self) -> [Option<T>; N];
71+
}
72+
73+
impl<T, S, const N: usize> From<S> for NullableArray<T, N>
74+
where
75+
S: IntoNullableArray<T, N>,
76+
{
77+
fn from(value: S) -> Self {
78+
Self(value.into_nullable_array())
79+
}
80+
}
81+
82+
impl<T, S, const N: usize> From<NullableArray<T, N>> for [Option<T>; N]
83+
where
84+
S: FromNullableArray<T, N>,
85+
{
86+
fn from(value: S) -> Self {
87+
value.
88+
}
89+
}
90+
91+
macro_rules! impl_array {
92+
(
93+
$(
94+
$rust_type:tt $(< $generic:ident >)? => $diesel_type:ident
95+
),*
96+
) => {
97+
$(
98+
impl<$($generic,)? const N: usize> Expression for Array<$rust_type$(<$generic>)?, N> {
99+
type SqlType = sql_types::Array<Nullable<$diesel_type>>;
100+
}
101+
102+
impl<$($generic,)? const N: usize> QueryId for Array<$rust_type$(<$generic>)?, N> {
103+
type QueryId = <sql_types::Array<Nullable<$diesel_type>> as QueryId>::QueryId;
104+
105+
const HAS_STATIC_QUERY_ID: bool = <sql_types::Array<Nullable<$diesel_type>> as QueryId>::HAS_STATIC_QUERY_ID;
106+
}
107+
108+
impl<$($generic: Debug + std::clone::Clone + Serialize,)? const N: usize> QueryFragment<Pg> for Array<$rust_type$(<$generic>)?, N>
109+
{
110+
fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
111+
pass.push_bind_param(self)?;
112+
Ok(())
113+
}
114+
}
115+
116+
impl<__QS, $($generic,)? const N: usize> AppearsOnTable<__QS> for Array<$rust_type$(<$generic>)?, N> {}
117+
118+
impl<__QS, $($generic,)? const N: usize> SelectableExpression<__QS> for Array<$rust_type$(<$generic>)?, N> {}
119+
120+
impl<$($generic: Debug + std::clone::Clone + Serialize,)? const N: usize> ToSql<sql_types::Array<Nullable<$diesel_type>>, Pg> for Array<$rust_type$(<$generic>)?, N>
121+
{
122+
fn to_sql<'b>(
123+
&'b self,
124+
out: &mut diesel::serialize::Output<'b, '_, Pg>,
125+
) -> diesel::serialize::Result {
126+
<[$rust_type $(< $generic >)?] as ToSql<sql_types::Array<$diesel_type>, Pg>>::to_sql(&self.0.as_slice(), out)
127+
}
128+
}
129+
130+
impl<$($generic: Debug + DeserializeOwned,)? const N: usize> FromSql<sql_types::Array<Nullable<$diesel_type>>, Pg> for Array<$rust_type$(< $generic >)?, N>
131+
{
132+
fn from_sql(bytes: PgValue<'_>) -> diesel::deserialize::Result<Self> {
133+
let raw = <Vec<Option<$rust_type $(< $generic >)?>> as FromSql<sql_types::Array<Nullable<$diesel_type>>, Pg>>::from_sql(bytes)?;
134+
135+
let res: [$rust_type $(< $generic >)?; N] = raw
136+
.into_iter()
137+
.collect::<Option<Vec<$rust_type $(< $generic >)?>>>()
138+
.ok_or(diesel::result::Error::DeserializationError(Box::new(
139+
InvalidArray::UnexpectedNullValue,
140+
)))?
141+
.try_into()
142+
.map_err(|_| {
143+
diesel::result::Error::DeserializationError(Box::new(
144+
InvalidArray::UnexpectedLength,
145+
))
146+
})?;
147+
148+
Ok(Self(res))
149+
}
150+
}
151+
)*
152+
}
153+
}
154+
155+
macro_rules! impl_nullable_array {
156+
(
157+
$(
158+
$rust_type:tt $(< $generic:ident >)? => $diesel_type:ident
159+
),*
160+
) => {
161+
$(
162+
impl<$($generic,)? const N: usize> Expression for NullableArray<$rust_type$(<$generic>)?, N> {
163+
type SqlType = sql_types::Array<Nullable<$diesel_type>>;
164+
}
165+
166+
impl<$($generic,)? const N: usize> QueryId for NullableArray<$rust_type$(<$generic>)?, N> {
167+
type QueryId = <sql_types::Array<Nullable<$diesel_type>> as QueryId>::QueryId;
168+
169+
const HAS_STATIC_QUERY_ID: bool = <sql_types::Array<Nullable<$diesel_type>> as QueryId>::HAS_STATIC_QUERY_ID;
170+
}
171+
172+
impl<$($generic: Debug + std::clone::Clone + Serialize,)? const N: usize> QueryFragment<Pg> for NullableArray<$rust_type$(<$generic>)?, N>
173+
{
174+
fn walk_ast<'b>(&'b self, mut pass: AstPass<'_, 'b, Pg>) -> QueryResult<()> {
175+
pass.push_bind_param(self)?;
176+
Ok(())
177+
}
178+
}
179+
180+
impl<__QS, $($generic,)? const N: usize> AppearsOnTable<__QS> for NullableArray<$rust_type$(<$generic>)?, N> {}
181+
impl<__QS, $($generic,)? const N: usize> SelectableExpression<__QS> for NullableArray<$rust_type$(<$generic>)?, N> {}
182+
183+
184+
impl<$($generic: Debug + std::clone::Clone + Serialize,)? const N: usize> ToSql<sql_types::Array<Nullable<$diesel_type>>, Pg> for NullableArray<$rust_type$(<$generic>)?, N>
185+
{
186+
fn to_sql<'b>(
187+
&'b self,
188+
out: &mut diesel::serialize::Output<'b, '_, Pg>,
189+
) -> diesel::serialize::Result {
190+
<[Option<$rust_type$(< $generic >)?>] as ToSql<sql_types::Array<Nullable<$diesel_type>>, Pg>>::to_sql(self.0.as_slice(), out)
191+
}
192+
}
193+
194+
impl<$($generic: Debug + DeserializeOwned,)? const N: usize> FromSql<sql_types::Array<Nullable<$diesel_type>>, Pg> for NullableArray<$rust_type$(< $generic >)?, N>
195+
{
196+
fn from_sql(bytes: PgValue<'_>) -> diesel::deserialize::Result<Self> {
197+
let raw = <Vec<Option<$rust_type$(< $generic >)?>> as FromSql<sql_types::Array<Nullable<$diesel_type>>, Pg>>::from_sql(bytes)?;
198+
199+
let res: [Option<$rust_type $(< $generic >)?>; N] = raw
200+
.try_into()
201+
.map_err(|_| {
202+
diesel::result::Error::DeserializationError(Box::new(
203+
InvalidArray::UnexpectedLength,
204+
))
205+
})?;
206+
207+
Ok(Self(res))
208+
}
209+
}
210+
)*
211+
};
212+
}
213+
214+
impl_array! {
215+
U15 => SmallInt,
216+
U31 => Integer,
217+
U63 => BigInt,
218+
i16 => SmallInt,
219+
i32 => Integer,
220+
i64 => BigInt,
221+
f32 => Float,
222+
f64 => Double,
223+
bool => Bool,
224+
String => Text,
225+
Json<S> => DbJson,
226+
Jsonb<S> => DbJsonb
227+
}
228+
229+
impl_nullable_array! {
230+
U15 => SmallInt,
231+
U31 => Integer,
232+
U63 => BigInt,
233+
i16 => SmallInt,
234+
i32 => Integer,
235+
i64 => BigInt,
236+
f32 => Float,
237+
f64 => Double,
238+
bool => Bool,
239+
String => Text,
240+
Json<S> => DbJson,
241+
Jsonb<S> => DbJsonb
242+
}

benzina/src/error.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,24 @@ impl Error for ParseIntError {
3838
}
3939
}
4040
}
41+
42+
#[derive(Debug, Clone)]
43+
pub enum InvalidArray {
44+
UnexpectedLength,
45+
UnexpectedNullValue,
46+
}
47+
48+
impl Display for InvalidArray {
49+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50+
f.write_str(match self {
51+
Self::UnexpectedLength => "mismatched array length",
52+
Self::UnexpectedNullValue => "the array contains an unexpected null value",
53+
})
54+
}
55+
}
56+
57+
impl Error for InvalidArray {
58+
fn source(&self) -> Option<&(dyn Error + 'static)> {
59+
None
60+
}
61+
}

benzina/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#[cfg(feature = "ctid")]
44
pub use self::ctid::{Ctid, ctid};
5+
#[cfg(feature = "array")]
6+
pub use self::array::{Array, NullableArray};
57
#[cfg(feature = "postgres")]
68
pub use self::int::{U15, U31, U63};
79
#[cfg(feature = "json")]
@@ -17,6 +19,8 @@ pub use benzina_derive::{Enum, join};
1719
pub mod __private;
1820
#[cfg(feature = "ctid")]
1921
mod ctid;
22+
#[cfg(feature = "array")]
23+
mod array;
2024
#[cfg(feature = "postgres")]
2125
pub mod error;
2226
#[cfg(feature = "example-generated")]

0 commit comments

Comments
 (0)