diesel_dynamic_schema/
dynamic_value.rs

1//! This module provides a container that allows to receive a dynamically
2//! specified number of fields from the database.
3//!
4//!
5//! ```rust
6//! # mod connection_setup {
7//! #     include!("../tests/connection_setup.rs");
8//! # }
9//! # use diesel::prelude::*;
10//! # use diesel::sql_types::{Untyped};
11//! # use diesel_dynamic_schema::{table, DynamicSelectClause};
12//! # use diesel_dynamic_schema::dynamic_value::*;
13//! # use diesel::dsl::sql_query;
14//! # use diesel::deserialize::{self, FromSql};
15//! #
16//! # #[derive(PartialEq, Debug)]
17//! # enum MyDynamicValue {
18//! #     String(String),
19//! #     Integer(i32),
20//! # }
21//! #
22//! # #[cfg(feature = "postgres")]
23//! # impl FromSql<Any, diesel::pg::Pg> for MyDynamicValue {
24//! #     fn from_sql(value: diesel::pg::PgValue) -> deserialize::Result<Self> {
25//! #         use diesel::pg::Pg;
26//! #         use std::num::NonZeroU32;
27//! #
28//! #         const VARCHAR_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1043) };
29//! #         const TEXT_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(25) };
30//! #         const INTEGER_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(23) };
31//! #
32//! #         match value.get_oid() {
33//! #             VARCHAR_OID | TEXT_OID => {
34//! #                 <String as FromSql<diesel::sql_types::Text, Pg>>::from_sql(value)
35//! #                     .map(MyDynamicValue::String)
36//! #             }
37//! #             INTEGER_OID => <i32 as FromSql<diesel::sql_types::Integer, Pg>>::from_sql(value)
38//! #                 .map(MyDynamicValue::Integer),
39//! #             e => Err(format!("Unknown type: {}", e).into()),
40//! #         }
41//! #     }
42//! # }
43//! #
44//! # #[cfg(feature = "sqlite")]
45//! # impl FromSql<Any, diesel::sqlite::Sqlite> for MyDynamicValue {
46//! #     fn from_sql(value: diesel::sqlite::SqliteValue) -> deserialize::Result<Self> {
47//! #         use diesel::sqlite::{Sqlite, SqliteType};
48//! #         match value.value_type() {
49//! #             Some(SqliteType::Text) => {
50//! #                 <String as FromSql<diesel::sql_types::Text, Sqlite>>::from_sql(value)
51//! #                     .map(MyDynamicValue::String)
52//! #             }
53//! #             Some(SqliteType::Long) => {
54//! #                 <i32 as FromSql<diesel::sql_types::Integer, Sqlite>>::from_sql(value)
55//! #                     .map(MyDynamicValue::Integer)
56//! #             }
57//! #             _ => Err("Unknown data type".into()),
58//! #         }
59//! #     }
60//! # }
61//! #
62//! # #[cfg(feature = "mysql")]
63//! # impl FromSql<Any, diesel::mysql::Mysql> for MyDynamicValue {
64//! #    fn from_sql(value: diesel::mysql::MysqlValue) -> deserialize::Result<Self> {
65//! #         use diesel::mysql::{Mysql, MysqlType};
66//! #         match value.value_type() {
67//! #              MysqlType::String => {
68//! #                  <String as FromSql<diesel::sql_types::Text, Mysql>>::from_sql(value)
69//! #                      .map(MyDynamicValue::String)
70//! #              }
71//! #              MysqlType::Long => <i32 as FromSql<diesel::sql_types::Integer, Mysql>>::from_sql(value)
72//! #                 .map(MyDynamicValue::Integer),
73//! #             e => Err(format!("Unknown data type: {:?}", e).into()),
74//! #         }
75//! #     }
76//! # }
77//! #
78//! # fn result_main() -> QueryResult<()> {
79//! #
80//! # let conn = &mut connection_setup::establish_connection();
81//! #
82//! # // Create some example data by using typical SQL statements.
83//! # connection_setup::create_user_table(conn);
84//! # // Create some example data by using typical SQL statements.
85//! # connection_setup::create_user_table(conn);
86//! # sql_query("INSERT INTO users (name) VALUES ('Sean'), ('Tess')").execute(conn)?;
87//!
88//!     let users = diesel_dynamic_schema::table("users");
89//!     let id = users.column::<Untyped, _>("id");
90//!     let name = users.column::<Untyped, _>("name");
91//!
92//!     let mut select = DynamicSelectClause::new();
93//!
94//!     select.add_field(id);
95//!     select.add_field(name);
96//!
97//!     let actual_data: Vec<DynamicRow<NamedField<MyDynamicValue>>> =
98//!         users.select(select).load(conn)?;
99//!
100//!     assert_eq!(
101//!         actual_data[0]["name"],
102//!         MyDynamicValue::String("Sean".into())
103//!     );
104//!     assert_eq!(
105//!         actual_data[0][1],
106//!         NamedField {
107//!             name: "name".into(),
108//!             value: MyDynamicValue::String("Sean".into())
109//!         }
110//!     );
111//!
112//! # Ok(())
113//! # }
114//! # result_main().unwrap()
115//! ```
116//!
117//! It is required to provide your own inner type to hold the actual database value.
118//!
119//! ```rust
120//! # use diesel_dynamic_schema::dynamic_value::Any;
121//! # use diesel::deserialize::{self, FromSql};
122//! #
123//! #[derive(PartialEq, Debug)]
124//! enum MyDynamicValue {
125//!    String(String),
126//!    Integer(i32),
127//! }
128//!
129//! # #[cfg(feature = "postgres")]
130//! impl FromSql<Any, diesel::pg::Pg> for MyDynamicValue {
131//!    fn from_sql(value: diesel::pg::PgValue) -> deserialize::Result<Self> {
132//!        use diesel::pg::Pg;
133//!        use std::num::NonZeroU32;
134//!
135//!        const VARCHAR_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(1043) };
136//!        const TEXT_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(25) };
137//!        const INTEGER_OID: NonZeroU32 = unsafe { NonZeroU32::new_unchecked(23) };
138//!
139//!        match value.get_oid() {
140//!            VARCHAR_OID | TEXT_OID => {
141//!                <String as FromSql<diesel::sql_types::Text, Pg>>::from_sql(value)
142//!                    .map(MyDynamicValue::String)
143//!            }
144//!            INTEGER_OID => <i32 as FromSql<diesel::sql_types::Integer, Pg>>::from_sql(value)
145//!                .map(MyDynamicValue::Integer),
146//!            e => Err(format!("Unknown type: {}", e).into()),
147//!        }
148//!    }
149//! }
150//! ```
151
152use diesel::backend::Backend;
153use diesel::deserialize::{self, FromSql};
154use diesel::expression::TypedExpressionType;
155use diesel::row::{Field, NamedRow, Row};
156use diesel::QueryableByName;
157use std::iter::FromIterator;
158use std::ops::Index;
159
160/// A marker type used to indicate that
161/// the provided `FromSql` impl does handle
162/// any passed database value, independently
163/// from the actual value kind
164pub struct Any;
165
166impl TypedExpressionType for Any {}
167
168#[cfg(feature = "postgres")]
169impl diesel::expression::QueryMetadata<Any> for diesel::pg::Pg {
170    fn row_metadata(_lookup: &mut Self::MetadataLookup, out: &mut Vec<Option<Self::TypeMetadata>>) {
171        out.push(None)
172    }
173}
174
175#[cfg(feature = "sqlite")]
176impl diesel::expression::QueryMetadata<Any> for diesel::sqlite::Sqlite {
177    fn row_metadata(_lookup: &mut Self::MetadataLookup, out: &mut Vec<Option<Self::TypeMetadata>>) {
178        out.push(None)
179    }
180}
181
182#[cfg(feature = "mysql")]
183impl diesel::expression::QueryMetadata<Any> for diesel::mysql::Mysql {
184    fn row_metadata(_lookup: &mut Self::MetadataLookup, out: &mut Vec<Option<Self::TypeMetadata>>) {
185        out.push(None)
186    }
187}
188
189/// A dynamically sized container that allows to receive
190/// a not at compile time known number of columns from the database
191#[derive(Debug)]
192pub struct DynamicRow<I> {
193    values: Vec<I>,
194}
195
196/// A helper struct used as field type in `DynamicRow`
197/// to also return the name of the field along with the
198/// value
199#[derive(Debug, PartialEq, Eq)]
200pub struct NamedField<I> {
201    /// Name of the field
202    pub name: String,
203    /// Actual field value
204    pub value: I,
205}
206
207impl<I> FromIterator<I> for DynamicRow<I> {
208    fn from_iter<T>(iter: T) -> Self
209    where
210        T: IntoIterator<Item = I>,
211    {
212        DynamicRow {
213            values: iter.into_iter().collect(),
214        }
215    }
216}
217
218impl<I> DynamicRow<I> {
219    /// Get the field value at the provided row index
220    ///
221    /// Returns `None` if the index is outside the bounds of the row
222    pub fn get(&self, index: usize) -> Option<&I> {
223        self.values.get(index)
224    }
225
226    /// Get the number of fields in the current row
227    pub fn len(&self) -> usize {
228        self.values.len()
229    }
230
231    /// Check if the current row is empty
232    pub fn is_empty(&self) -> bool {
233        self.values.is_empty()
234    }
235
236    /// Create a new dynamic row from an existing database row
237    ///
238    /// This function is mostly useful for third party backends adding
239    /// support for `diesel_dynamic_schema`
240    pub fn from_row<'a, DB>(row: &impl Row<'a, DB>) -> deserialize::Result<Self>
241    where
242        DB: Backend,
243        I: FromSql<Any, DB>,
244    {
245        let data = (0..row.field_count())
246            .map(|i| {
247                let field = Row::get(row, i).expect("We checked the field count above");
248
249                I::from_nullable_sql(field.value())
250            })
251            .collect::<deserialize::Result<_>>()?;
252
253        Ok(Self { values: data })
254    }
255}
256
257impl<I> DynamicRow<NamedField<I>> {
258    /// Get the field value by the provided field name
259    ///
260    /// Returns `None` if the field with the specified name is not found.
261    /// If there are multiple fields with the same name, the behaviour
262    /// of this function is unspecified.
263    pub fn get_by_name<S: AsRef<str>>(&self, name: S) -> Option<&I> {
264        self.values
265            .iter()
266            .find(|f| f.name == name.as_ref())
267            .map(|f| &f.value)
268    }
269}
270
271impl<I> DynamicRow<NamedField<Option<I>>> {
272    /// Create a new dynamic row instance with corresponding field information from the given
273    /// database row
274    ///
275    /// This function is mostly useful for third party backends adding
276    /// support for `diesel_dynamic_schema`
277    pub fn from_nullable_row<'a, DB>(row: &impl Row<'a, DB>) -> deserialize::Result<Self>
278    where
279        DB: Backend,
280        I: FromSql<Any, DB>,
281    {
282        let data = (0..row.field_count())
283            .map(|i| {
284                let field = Row::get(row, i).expect("We checked the field count above");
285
286                let value = match I::from_nullable_sql(field.value()) {
287                    Ok(o) => Some(o),
288                    Err(e) if e.is::<diesel::result::UnexpectedNullError>() => None,
289                    Err(e) => return Err(e),
290                };
291
292                Ok(NamedField {
293                    name: field
294                        .field_name()
295                        .ok_or("Try to load an unnamed field")?
296                        .to_owned(),
297                    value,
298                })
299            })
300            .collect::<deserialize::Result<Vec<_>>>()?;
301        Ok(DynamicRow { values: data })
302    }
303}
304
305#[cfg(feature = "postgres")]
306impl<I> QueryableByName<diesel::pg::Pg> for DynamicRow<I>
307where
308    I: FromSql<Any, diesel::pg::Pg>,
309{
310    fn build<'a>(row: &impl NamedRow<'a, diesel::pg::Pg>) -> deserialize::Result<Self> {
311        Self::from_row(row)
312    }
313}
314
315#[cfg(feature = "mysql")]
316impl<I> QueryableByName<diesel::mysql::Mysql> for DynamicRow<I>
317where
318    I: FromSql<Any, diesel::mysql::Mysql>,
319{
320    fn build<'a>(row: &impl NamedRow<'a, diesel::mysql::Mysql>) -> deserialize::Result<Self> {
321        Self::from_row(row)
322    }
323}
324
325#[cfg(feature = "sqlite")]
326impl<I> QueryableByName<diesel::sqlite::Sqlite> for DynamicRow<I>
327where
328    I: FromSql<Any, diesel::sqlite::Sqlite>,
329{
330    fn build<'a>(row: &impl NamedRow<'a, diesel::sqlite::Sqlite>) -> deserialize::Result<Self> {
331        Self::from_row(row)
332    }
333}
334
335impl<I, DB> QueryableByName<DB> for DynamicRow<Option<I>>
336where
337    DB: Backend,
338    I: FromSql<Any, DB>,
339{
340    fn build<'a>(row: &impl NamedRow<'a, DB>) -> deserialize::Result<Self> {
341        let data = (0..row.field_count())
342            .map(|i| {
343                let field = Row::get(row, i).expect("We checked the field count above");
344
345                match I::from_nullable_sql(field.value()) {
346                    Ok(o) => Ok(Some(o)),
347                    Err(e) if e.is::<diesel::result::UnexpectedNullError>() => Ok(None),
348                    Err(e) => Err(e),
349                }
350            })
351            .collect::<deserialize::Result<_>>()?;
352
353        Ok(Self { values: data })
354    }
355}
356
357impl<I, DB> QueryableByName<DB> for DynamicRow<NamedField<I>>
358where
359    DB: Backend,
360    I: FromSql<Any, DB>,
361{
362    fn build<'a>(row: &impl NamedRow<'a, DB>) -> deserialize::Result<Self> {
363        let data = (0..row.field_count())
364            .map(|i| {
365                let field = Row::get(row, i).expect("We checked the field count above");
366
367                let value = I::from_nullable_sql(field.value())?;
368
369                Ok(NamedField {
370                    name: field
371                        .field_name()
372                        .ok_or("Try to load an unnamed field")?
373                        .to_owned(),
374                    value,
375                })
376            })
377            .collect::<deserialize::Result<Vec<_>>>()?;
378        Ok(DynamicRow { values: data })
379    }
380}
381
382#[cfg(feature = "postgres")]
383impl<I> QueryableByName<diesel::pg::Pg> for DynamicRow<NamedField<Option<I>>>
384where
385    I: FromSql<Any, diesel::pg::Pg>,
386{
387    fn build<'a>(row: &impl NamedRow<'a, diesel::pg::Pg>) -> deserialize::Result<Self> {
388        Self::from_nullable_row(row)
389    }
390}
391
392#[cfg(feature = "mysql")]
393impl<I> QueryableByName<diesel::mysql::Mysql> for DynamicRow<NamedField<Option<I>>>
394where
395    I: FromSql<Any, diesel::mysql::Mysql>,
396{
397    fn build<'a>(row: &impl NamedRow<'a, diesel::mysql::Mysql>) -> deserialize::Result<Self> {
398        Self::from_nullable_row(row)
399    }
400}
401
402#[cfg(feature = "sqlite")]
403impl<I> QueryableByName<diesel::sqlite::Sqlite> for DynamicRow<NamedField<Option<I>>>
404where
405    I: FromSql<Any, diesel::sqlite::Sqlite>,
406{
407    fn build<'a>(row: &impl NamedRow<'a, diesel::sqlite::Sqlite>) -> deserialize::Result<Self> {
408        Self::from_nullable_row(row)
409    }
410}
411
412impl<I> Index<usize> for DynamicRow<I> {
413    type Output = I;
414
415    fn index(&self, index: usize) -> &Self::Output {
416        &self.values[index]
417    }
418}
419
420impl<'a, I> Index<&'a str> for DynamicRow<NamedField<I>> {
421    type Output = I;
422
423    fn index(&self, field_name: &'a str) -> &Self::Output {
424        self.values
425            .iter()
426            .find(|f| f.name == field_name)
427            .map(|f| &f.value)
428            .expect("Field not found")
429    }
430}
431
432impl<'a, I> Index<&'a String> for DynamicRow<NamedField<I>> {
433    type Output = I;
434
435    fn index(&self, field_name: &'a String) -> &Self::Output {
436        self.index(field_name as &str)
437    }
438}
439
440impl<I> Index<String> for DynamicRow<NamedField<I>> {
441    type Output = I;
442
443    fn index(&self, field_name: String) -> &Self::Output {
444        self.index(&field_name)
445    }
446}
447
448impl<V> IntoIterator for DynamicRow<V> {
449    type Item = V;
450    type IntoIter = <Vec<V> as IntoIterator>::IntoIter;
451
452    fn into_iter(self) -> Self::IntoIter {
453        self.values.into_iter()
454    }
455}
456
457impl<'a, V> IntoIterator for &'a DynamicRow<V> {
458    type Item = &'a V;
459    type IntoIter = <&'a Vec<V> as IntoIterator>::IntoIter;
460
461    fn into_iter(self) -> Self::IntoIter {
462        self.values.iter()
463    }
464}